#!/usr/bin/env python3

import argparse
import json
import subprocess
import xml.etree.ElementTree


def ignore_feature(feature):
    ignored_features = [
        # VIA/Cyrix/Centaur-defined CPU features
        # CPUID level 0xC0000001, word 5
        "ace2",
        "ace2-en",
        "phe",
        "phe-en",
        "pmm",
        "pmm-en",
        "xcrypt",
        "xcrypt-en",
        "xstore",
        "xstore-en",

        # non-features
        "check",
        "cpuid-0xb",
        "enforce",
        "fill-mtrr-mask",
        "full-cpuid-auto-level",
        "full-width-write",
        "host-cache-info",
        "host-phys-bits",
        "hotpluggable",
        "hotplugged",
        "hv-apicv",
        "hv-avic",
        "hv-crash",
        "hv-emsr-bitmap",
        "hv-enforce-cpuid",
        "hv-evmcs",
        "hv-frequencies",
        "hv-ipi",
        "hv-passthrough",
        "hv-reenlightenment",
        "hv-relaxed",
        "hv-reset",
        "hv-runtime",
        "hv-stimer",
        "hv-stimer-direct",
        "hv-syndbg",
        "hv-synic",
        "hv-time",
        "hv-tlbflush",
        "hv-tlbflush-direct",
        "hv-tlbflush-ext",
        "hv-vapic",
        "hv-vpindex",
        "hv-xmm-input",
        "kvm",
        "kvm-asyncpf",
        "kvm-asyncpf-int",
        "kvm-hint-dedicated",
        "kvm-mmu",
        "kvm-msi-ext-dest-id",
        "kvm-no-smi-migration",
        "kvm-nopiodelay",
        "kvm-poll-control",
        "kvm-pv-enforce-cpuid",
        "kvm-pv-eoi",
        "kvm-pv-ipi",
        "kvm-pv-sched-yield",
        "kvm-pv-tlb-flush",
        "kvm-pv-unhalt",
        "kvm-steal-time",
        "kvm_asyncpf",
        "kvm_asyncpf_int",
        "kvm_mmu",
        "kvm_nopiodelay",
        "kvm_poll_control",
        "kvm_pv_eoi",
        "kvm_pv_unhalt",
        "kvm_steal_time",
        "kvmclock",
        "kvmclock-stable-bit",
        "l3-cache",
        "legacy-cache",
        "lmce",
        "migratable",
        "pmu",
        "realized",
        "start-powered-off",
        "tcg-cpuid",
        "vmware-cpuid-freq",
        "vmx-activity-hlt",
        "vmx-activity-shutdown",
        "vmx-activity-wait-sipi",
        "vmx-apicv-register",
        "vmx-apicv-vid",
        "vmx-apicv-x2apic",
        "vmx-apicv-xapic",
        "vmx-cr3-load-noexit",
        "vmx-cr3-store-noexit",
        "vmx-cr8-load-exit",
        "vmx-cr8-store-exit",
        "vmx-desc-exit",
        "vmx-encls-exit",
        "vmx-entry-ia32e-mode",
        "vmx-entry-load-bndcfgs",
        "vmx-entry-load-efer",
        "vmx-entry-load-pat",
        "vmx-entry-load-perf-global-ctrl",
        "vmx-entry-load-pkrs",
        "vmx-entry-load-rtit-ctl",
        "vmx-entry-noload-debugctl",
        "vmx-ept",
        "vmx-ept-1gb",
        "vmx-ept-2mb",
        "vmx-ept-advanced-exitinfo",
        "vmx-ept-execonly",
        "vmx-eptad",
        "vmx-eptp-switching",
        "vmx-exit-ack-intr",
        "vmx-exit-clear-bndcfgs",
        "vmx-exit-clear-rtit-ctl",
        "vmx-exit-load-efer",
        "vmx-exit-load-pat",
        "vmx-exit-load-perf-global-ctrl",
        "vmx-exit-load-pkrs",
        "vmx-exit-nosave-debugctl",
        "vmx-exit-save-efer",
        "vmx-exit-save-pat",
        "vmx-exit-save-preemption-timer",
        "vmx-flexpriority",
        "vmx-hlt-exit",
        "vmx-ins-outs",
        "vmx-intr-exit",
        "vmx-invept",
        "vmx-invept-all-context",
        "vmx-invept-single-context",
        "vmx-invept-single-context-noglobals",
        "vmx-invlpg-exit",
        "vmx-invpcid-exit",
        "vmx-invvpid",
        "vmx-invvpid-all-context",
        "vmx-invvpid-single-addr",
        "vmx-io-bitmap",
        "vmx-io-exit",
        "vmx-monitor-exit",
        "vmx-movdr-exit",
        "vmx-msr-bitmap",
        "vmx-mtf",
        "vmx-mwait-exit",
        "vmx-nmi-exit",
        "vmx-page-walk-4",
        "vmx-page-walk-5",
        "vmx-pause-exit",
        "vmx-ple",
        "vmx-pml",
        "vmx-posted-intr",
        "vmx-preemption-timer",
        "vmx-rdpmc-exit",
        "vmx-rdrand-exit",
        "vmx-rdseed-exit",
        "vmx-rdtsc-exit",
        "vmx-rdtscp-exit",
        "vmx-secondary-ctls",
        "vmx-shadow-vmcs",
        "vmx-store-lma",
        "vmx-true-ctls",
        "vmx-tsc-offset",
        "vmx-tsc-scaling",
        "vmx-unrestricted-guest",
        "vmx-vintr-pending",
        "vmx-vmfunc",
        "vmx-vmwrite-vmexit-fields",
        "vmx-vnmi",
        "vmx-vnmi-pending",
        "vmx-vpid",
        "vmx-wbinvd-exit",
        "vmx-xsaves",
        "vmx-zero-len-inject",
    ]

    if feature["type"] != "bool":
        return True

    name = feature["name"]

    if name.startswith("x-"):
        return True

    if name in ignored_features:
        return True

    return False


def get_qemu_feature_list(path_to_qemu):
    cmd = [
        path_to_qemu,
        "-machine", "accel=kvm",
        "-cpu", "host",
        "-nodefaults",
        "-nographic",
        "-qmp",
        "stdio"
    ]

    request = """
    {
        "execute": "qmp_capabilities"
    }
    {
        "execute": "qom-list-properties",
        "arguments": {
            "typename": "max-x86_64-cpu"
        },
        "id": "qom-list-properties"
    }
    {
        "execute": "quit"
    }
    """

    decoder = json.JSONDecoder()
    output = subprocess.check_output(cmd, input=request, text=True)
    while output:
        obj, idx = decoder.raw_decode(output)
        output = output[idx:].strip()
        if obj.get("id") != "qom-list-properties":
            continue
        for feature in obj["return"]:
            if ignore_feature(feature):
                continue
            yield feature["name"]


def get_libvirt_feature_list(path_to_featuresfile):
    dom = xml.etree.ElementTree.parse(path_to_featuresfile)
    for feature in dom.getroot().iter("feature"):
        yield feature.get("name")
        for alias in feature:
            if alias.tag == "alias" and alias.get("source") == "qemu":
                yield alias.get("name")


def main():
    parser = argparse.ArgumentParser(
        description="Synchronize x86 cpu features from QEMU i386 target.")
    parser.add_argument(
        "--qemu",
        help="Path to qemu executable",
        default="qemu-system-x86_64",
        type=str)
    parser.add_argument(
        "--features",
        help="Path to 'src/cpu_map/x86_features.xml' file in "
             "the libvirt repository",
        default="x86_features.xml",
        type=str)

    args = parser.parse_args()

    qfeatures = get_qemu_feature_list(args.qemu)
    lfeatures = list(get_libvirt_feature_list(args.features))
    missing = [f for f in sorted(qfeatures) if f not in lfeatures]

    if missing:
        print("The following features were reported by qemu but are "
              "unknown to libvirt:")
        for feature in missing:
            print(" *", feature)

    return len(missing) != 0


if __name__ == "__main__":
    exit(main())
