virDomainDefFeaturesCheckABIStability(virDomainDefPtr src,
                                      virDomainDefPtr dst)
{
    size_t i;

    for (i = 0; i < VIR_DOMAIN_FEATURE_LAST; i++) {
        const char *featureName = virDomainFeatureTypeToString(i);

        switch ((virDomainFeature) i) {
        case VIR_DOMAIN_FEATURE_ACPI:
        case VIR_DOMAIN_FEATURE_PAE:
        case VIR_DOMAIN_FEATURE_HAP:
        case VIR_DOMAIN_FEATURE_VIRIDIAN:
        case VIR_DOMAIN_FEATURE_PRIVNET:
        case VIR_DOMAIN_FEATURE_HYPERV:
        case VIR_DOMAIN_FEATURE_KVM:
        case VIR_DOMAIN_FEATURE_PVSPINLOCK:
        case VIR_DOMAIN_FEATURE_PMU:
        case VIR_DOMAIN_FEATURE_VMPORT:
        case VIR_DOMAIN_FEATURE_SMM:
        case VIR_DOMAIN_FEATURE_VMCOREINFO:
        case VIR_DOMAIN_FEATURE_HTM:
        case VIR_DOMAIN_FEATURE_NESTED_HV:
        case VIR_DOMAIN_FEATURE_CCF_ASSIST:
            if (src->features[i] != dst->features[i]) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("State of feature '%s' differs: "
                                 "source: '%s', destination: '%s'"),
                               featureName,
                               virTristateSwitchTypeToString(src->features[i]),
                               virTristateSwitchTypeToString(dst->features[i]));
                return false;
            }
            break;

        case VIR_DOMAIN_FEATURE_CAPABILITIES:
            if (src->features[i] != dst->features[i]) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("State of feature '%s' differs: "
                                 "source: '%s=%s', destination: '%s=%s'"),
                               featureName,
                               "policy",
                               virDomainCapabilitiesPolicyTypeToString(src->features[i]),
                               "policy",
                               virDomainCapabilitiesPolicyTypeToString(dst->features[i]));
                return false;
            }
            break;

        case VIR_DOMAIN_FEATURE_GIC:
            if (src->features[i] != dst->features[i] ||
                src->gic_version != dst->gic_version) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("State of feature '%s' differs: "
                                 "source: '%s,%s=%s', destination: '%s,%s=%s'"),
                               featureName,
                               virTristateSwitchTypeToString(src->features[i]),
                               "version", virGICVersionTypeToString(src->gic_version),
                               virTristateSwitchTypeToString(dst->features[i]),
                               "version", virGICVersionTypeToString(dst->gic_version));
                return false;
            }
            break;

        case VIR_DOMAIN_FEATURE_HPT:
            if (src->features[i] != dst->features[i] ||
                src->hpt_resizing != dst->hpt_resizing ||
                src->hpt_maxpagesize != dst->hpt_maxpagesize) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("State of feature '%s' differs: "
                                 "source: '%s,%s=%s,%s=%llu', destination: '%s,%s=%s,%s=%llu'"),
                               featureName,
                               virTristateSwitchTypeToString(src->features[i]),
                               "resizing", virDomainHPTResizingTypeToString(src->hpt_resizing),
                               "maxpagesize", src->hpt_maxpagesize,
                               virTristateSwitchTypeToString(dst->features[i]),
                               "resizing", virDomainHPTResizingTypeToString(dst->hpt_resizing),
                               "maxpagesize", dst->hpt_maxpagesize);
                return false;
            }
            break;

        case VIR_DOMAIN_FEATURE_APIC:
            if (src->features[i] != dst->features[i] ||
                src->apic_eoi != dst->apic_eoi) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("State of feature '%s' differs: "
                                 "source: '%s,%s=%s', destination: '%s,%s=%s'"),
                               featureName,
                               virTristateSwitchTypeToString(src->features[i]),
                               "eoi", virTristateSwitchTypeToString(src->apic_eoi),
                               virTristateSwitchTypeToString(dst->features[i]),
                               "eoi", virTristateSwitchTypeToString(dst->apic_eoi));
                return false;
            }
            break;

        case VIR_DOMAIN_FEATURE_IOAPIC:
            if (src->features[i] != dst->features[i]) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("State of feature '%s' differs: "
                                 "source: '%s=%s', destination: '%s=%s'"),
                               featureName,
                               "driver", virDomainIOAPICTypeToString(src->features[i]),
                               "driver", virDomainIOAPICTypeToString(dst->features[i]));
                return false;
            }
            break;

        case VIR_DOMAIN_FEATURE_MSRS:
            break;

        case VIR_DOMAIN_FEATURE_LAST:
            break;
        }
    }

    /* hyperv */
    if (src->features[VIR_DOMAIN_FEATURE_HYPERV] == VIR_TRISTATE_SWITCH_ON) {
        for (i = 0; i < VIR_DOMAIN_HYPERV_LAST; i++) {
            switch ((virDomainHyperv) i) {
            case VIR_DOMAIN_HYPERV_RELAXED:
            case VIR_DOMAIN_HYPERV_VAPIC:
            case VIR_DOMAIN_HYPERV_VPINDEX:
            case VIR_DOMAIN_HYPERV_RUNTIME:
            case VIR_DOMAIN_HYPERV_SYNIC:
            case VIR_DOMAIN_HYPERV_STIMER:
            case VIR_DOMAIN_HYPERV_RESET:
            case VIR_DOMAIN_HYPERV_FREQUENCIES:
            case VIR_DOMAIN_HYPERV_REENLIGHTENMENT:
            case VIR_DOMAIN_HYPERV_TLBFLUSH:
            case VIR_DOMAIN_HYPERV_IPI:
            case VIR_DOMAIN_HYPERV_EVMCS:
                if (src->hyperv_features[i] != dst->hyperv_features[i]) {
                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                                   _("State of HyperV enlightenment "
                                     "feature '%s' differs: "
                                     "source: '%s', destination: '%s'"),
                                   virDomainHypervTypeToString(i),
                                   virTristateSwitchTypeToString(src->hyperv_features[i]),
                                   virTristateSwitchTypeToString(dst->hyperv_features[i]));
                    return false;
                }

                break;

            case VIR_DOMAIN_HYPERV_SPINLOCKS:
                /* spinlock count matters! */
                if (src->hyperv_spinlocks != dst->hyperv_spinlocks) {
                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                                   _("HyperV spinlock retry count differs: "
                                     "source: '%u', destination: '%u'"),
                                   src->hyperv_spinlocks,
                                   dst->hyperv_spinlocks);
                    return false;
                }
                break;

            case VIR_DOMAIN_HYPERV_VENDOR_ID:
                if (STRNEQ_NULLABLE(src->hyperv_vendor_id, dst->hyperv_vendor_id)) {
                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                                   _("HyperV vendor_id differs: "
                                     "source: '%s', destination: '%s'"),
                                   src->hyperv_vendor_id,
                                   dst->hyperv_vendor_id);
                    return false;
                }
                break;

            /* coverity[dead_error_begin] */
            case VIR_DOMAIN_HYPERV_LAST:
                break;
            }
        }
    }

    if (src->hyperv_features[VIR_DOMAIN_HYPERV_STIMER] == VIR_TRISTATE_SWITCH_ON) {
        if (src->hyperv_stimer_direct != dst->hyperv_stimer_direct) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("State of HyperV stimer direct feature differs: "
                             "source: '%s', destination: '%s'"),
                           virTristateSwitchTypeToString(src->hyperv_stimer_direct),
                           virTristateSwitchTypeToString(dst->hyperv_stimer_direct));
            return false;
        }
    }

    /* kvm */
    if (src->features[VIR_DOMAIN_FEATURE_KVM] == VIR_TRISTATE_SWITCH_ON) {
        for (i = 0; i < VIR_DOMAIN_KVM_LAST; i++) {
            switch ((virDomainKVM) i) {
            case VIR_DOMAIN_KVM_HIDDEN:
            case VIR_DOMAIN_KVM_DEDICATED:
                if (src->kvm_features[i] != dst->kvm_features[i]) {
                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                                   _("State of KVM feature '%s' differs: "
                                     "source: '%s', destination: '%s'"),
                                   virDomainKVMTypeToString(i),
                                   virTristateSwitchTypeToString(src->kvm_features[i]),
                                   virTristateSwitchTypeToString(dst->kvm_features[i]));
                    return false;
                }

                break;

            /* coverity[dead_error_begin] */
            case VIR_DOMAIN_KVM_LAST:
                break;
            }
        }
    }

    /* smm */
    if (src->features[VIR_DOMAIN_FEATURE_SMM] == VIR_TRISTATE_SWITCH_ON) {
        if (src->tseg_specified != dst->tseg_specified) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("SMM TSEG differs: source: %s, destination: '%s'"),
                           src->tseg_specified ? _("specified") : _("not specified"),
                           dst->tseg_specified ? _("specified") : _("not specified"));
            return false;
        }

        if (src->tseg_specified &&
            src->tseg_size != dst->tseg_size) {
            const char *unit_src, *unit_dst;
            unsigned long long short_size_src = virFormatIntPretty(src->tseg_size,
                                                                   &unit_src);
            unsigned long long short_size_dst = virFormatIntPretty(dst->tseg_size,
                                                                   &unit_dst);

            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Size of SMM TSEG size differs: "
                             "source: '%llu %s', destination: '%llu %s'"),
                           short_size_src, unit_src,
                           short_size_dst, unit_dst);
            return false;
        }
    }

    return true;
}