[
  {
    "path": ".gitignore",
    "content": "*.1.gz\n*.1.pod\n*.5.gz\n*.5.pod\n*.buildinfo\n*.changes\n*.deb\n/.vscode/\n/qemu-server-[0-9]*/\nbuild\nqm.bash-completion\nsparsecp\nvmtar\n"
  },
  {
    "path": "Makefile",
    "content": "include /usr/share/dpkg/default.mk\n\nexport PACKAGE=qemu-server\nBUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)\n\nexport DESTDIR ?=\n\nGITVERSION:=$(shell git rev-parse HEAD)\n\nDEB=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION)_$(DEB_HOST_ARCH).deb\nDBG_DEB=$(PACKAGE)-dbgsym_$(DEB_VERSION_UPSTREAM_REVISION)_$(DEB_HOST_ARCH).deb\nDSC=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION).dsc\n\nDEBS=$(DEB) $(DBG_DEB)\n\nall:\n\n.PHONY: tidy\ntidy:\n\tgit ls-files ':*.p[ml]'| xargs -n4 -P0 proxmox-perltidy\n\tcd src; proxmox-perltidy bin/qm bin/qmextract bin/qmrestore usr/pve-bridgedown usr/pve-bridge\n\n.PHONY: dinstall\ndinstall: deb\n\tdpkg -i $(DEB)\n\n$(BUILDDIR):\n\trm -rf $@ $@.tmp\n\tcp -a src $@.tmp\n\tcp -a debian $@.tmp/\n\techo \"git clone git://git.proxmox.com/git/qemu-server.git\\\\ngit checkout $(GITVERSION)\" > $@.tmp/debian/SOURCE\n\tmv $@.tmp $@\n\n.PHONY: deb\ndeb: $(DEBS)\n$(DBG_DEB): $(DEB)\n$(DEB): $(BUILDDIR)\n\tcd $(BUILDDIR); dpkg-buildpackage -b -us -uc\n\tlintian $(DEBS)\n\n.PHONY: dsc\ndsc: $(DSC)\n$(DSC): $(BUILDDIR)\n\tcd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d\n\tlintian $(DSC)\n\nsbuild: $(DSC)\n\tsbuild $(DSC)\n\n.PHONY: upload\nupload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)\nupload: $(DEB)\n\ttar cf - $(DEBS) | ssh -X repoman@repo.proxmox.com upload --product pve --dist $(UPLOAD_DIST) --arch $(DEB_HOST_ARCH)\n\n.PHONY: clean\nclean:\n\trm -rf $(PACKAGE)-*/ *.deb *.build *.buildinfo *.changes *.dsc $(PACKAGE)_*.tar.?z\n\n\n.PHONY: distclean\ndistclean: clean\n"
  },
  {
    "path": "debian/changelog",
    "content": "qemu-server (9.1.9) trixie; urgency=medium\n\n  * qmp client: set default timeouts for more block-related commands\n    ('block-commit', 'block-stream', 'blockdev-reopen', and others) to avoid\n    the previous five-second fallback failing prematurely while the operation\n    waits for in-flight I/O to drain.\n\n  * fix #7066: api: allow live snapshot create and remove for a qcow2 TPM\n    state drive on a storage with the 'snapshot-as-volume-chain' option\n    enabled, by routing the block-level QMP commands for the TPM disk to the\n    QEMU storage daemon that holds the FUSE-exported state. Removing the\n    top-most snapshot live remains unsupported, as the FUSE export holds the\n    'resize' permission that 'block-commit' and 'block-stream' require.\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 25 Apr 2026 19:46:01 +0200\n\nqemu-server (9.1.8) trixie; urgency=medium\n\n  * cloud-init: dns: avoid \"uninitialized value in split\" warning if there is\n    no 'searchdomain' cloud-init setting and also no 'search' setting in the\n    host's resolv.conf.\n\n  * api: dump cloud-init: mask password for user without VM.Config.Cloudinit\n    privilege.\n\n  * api: confirm Sys.Console privilege when creating or restoring a VM and\n    actively enabling HA for it.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Apr 2026 16:27:25 +0200\n\nqemu-server (9.1.7) trixie; urgency=medium\n\n  * start vm: clarify the informational BitLocker message to state that\n    protectors must be disabled before enrolling UEFI keys, matching the\n    existing wording in the web UI and documentation.\n\n  * hotplug pending: avoid the USB-leftover and USB-hotplug-support checks,\n    along with the associated QMP calls, for unrelated config changes that do\n    not touch USB devices. Explicitly clean up a newly-plugged 'xhci'\n    controller in the error path of a failing USB hotplug, now that the\n    implicit cleanup is gone.\n\n  * migration: only warn about a missing 'host_mtu' setting on a network\n    device when it is actually unexpected, to avoid confusing log noise on\n    legacy machine versions with default MTU.\n\n  * backup: log 'starting backup via QMP command' between the filesystem\n    freeze and thaw messages, to make it obvious that the ordering of freeze,\n    backup start, and thaw is correct.\n\n  * backup: vma: prefix the bubbled-up QEMU error on backup start with\n    'executing QMP command to start backup failed -', to make the failing\n    operation easier to identify.\n\n  * cpu config: warn when a VM uses a qemu64- or kvm64-derived CPU model\n    without the 'pdpe1gb' flag, as OVMF will then limit the guest to 40\n    phys-bits, which can prevent booting VMs with large amounts of memory or\n    with GPU passthrough using lots of vRAM.\n\n  * api: disk move: log the core operations (allocate, copy, rename, remove\n    source) in the task log; clone disk: log the target volume ID after\n    allocating; api: disk reassign: include the target disk key and use\n    'reassign' in the log message to align with the UI term.\n\n  * api: vncproxy: align the VM-specific vncproxy endpoint for non-serial\n    displays with the serial-display behavior: listen only on localhost when\n    used through the 'websocket' parameter, and use TLS otherwise. This may\n    affect external VNC clients that connect directly without going through\n    'vncwebsocket' or that do not support TLS.\n\n  * api: vnc/termproxy: bind VNC and SPICE tickets to the listen port to\n    harden authentication for interactive console sessions. Also extends to\n    the SPICE proxy ticket used during live migration.\n\n  * api: vncproxy: always use a freshly generated password when authenticating\n    via the VNC protocol, improving entropy compared to the previous\n    ticket-derived scheme.\n\n  * api: termproxy: pin the accepted ticket in the spawned termproxy process\n    as an additional defense-in-depth measure.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 21 Apr 2026 22:33:39 +0200\n\nqemu-server (9.1.6) trixie; urgency=medium\n\n  * guest agent: rename 'guest-fsfreeze' setting to 'freeze-fs' for\n    consistency with the existing agent sub-property naming. The old name and\n    'freeze-fs-on-backup' are kept as aliases.\n\n  * guest agent: fix clone, import and backup not properly checking whether\n    the agent is actually enabled before attempting to freeze guest file\n    systems.\n\n  * guest agent: fs thaw: check command result instead of silently ignoring\n    errors.\n\n  * api: clone: log the new VMID at the start of the task, as it was not\n    recorded in the task log, system log, or inside the task worker UPID\n    before.\n\n  * external migration: block if HA is configured regardless of HA management\n    state, as migrating an HA-managed VM can confuse the HA stack even when in\n    'ignored' state.\n\n  * query cpu flags: fix KVM support detection and TCG model selection for\n    non-x86 hosts, and add support for querying CPU flags on aarch64.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Mar 2026 23:43:39 +0100\n\nqemu-server (9.1.5) trixie; urgency=medium\n\n  * fix #1964: add 'guest-fsfreeze' QEMU guest agent setting to control\n    whether filesystem freeze/thaw commands are issued for snapshots,\n    replications and clones — not just backups. The previous\n    'freeze-fs-on-backup' setting is deprecated in favor of the new one.\n\n  * agent: file-read: allow specifying a byte offset and maximum byte count,\n    and allow keeping the base64-encoding of the response content. This\n    enables reading large files block-by-block and avoids inflated payload\n    sizes for binary data.\n\n  * extend and improve experimental aarch64 VM support. Allow querying CPU\n    models, CPU flags and machine types for a given architecture via the API.\n    Add aarch64 CPU models. Guard x86-specific CPU options like hyperv\n    enlightenments and the 'hidden' flag by architecture. Fix S3/S4 ACPI power\n    state not being disabled for ARM-based virt machines, as ARM has no\n    concept of those. Fix machine version pinning and default architecture\n    detection on non-x86 hosts.\n\n  * vm start: check efi: extend the UEFI 2023 certificate enrollment check to\n    all VMs with pre-enrolled keys, not just Windows 10 and 11, as common\n    Linux distributions also require the updated certificates for secure boot.\n\n  * ovmf: also enroll the Microsoft Corporation KEK 2K CA 2023 key exchange\n    key, as not having it can still cause secure boot update failures.\n\n  * apply pending config changes: efi disk: fix ms-cert marker not being\n    updated in config after certificate enrollment via API, which could leave\n    a stale marker.\n\n  * vm status: guard memory stat collection with eval to avoid breaking the\n    whole VM status API call or pvestatd collection if it fails.\n\n  * verify device removal: mention that the device might still be busy in the\n    guest on timeout, which is a common cause especially on Windows with\n    ongoing IO on VirtIO block devices.\n\n  * qmp: switch from deprecated block-job-complete to job-complete command.\n\n  * blockdev: introduce dedicated module for snapshot-as-volume-chain handling\n    to resolve cyclic dependency between Blockdev and BlockJob modules.\n\n  * config: document default balloon behavior and fix 'ballon' typo.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 06 Mar 2026 12:48:12 +0100\n\nqemu-server (9.1.4.1) trixie; urgency=medium\n\n  * vm status: guard memory stat collection with eval to avoid breaking the\n    whole VM status API call or pvestatd collection if it fails.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 30 Mar 2026 09:21:37 +0200\n\nqemu-server (9.1.4) trixie; urgency=medium\n\n  * vga: allow live-migration with clipboard with QEMU 10.1, which gained\n    support for doing so.\n\n  * fix #6935: vmstatus: use CGroup for host memory usage to avoid performance\n    impact of reading the PSS metrics when there is a significant amount of\n    kernel same-page memory being merged.\n\n  * migration: vm start: avoid config write when there are left-over VM-state\n    related properties.\n\n  * config: fix OVMF typo in error message.\n\n  * cpu config: update CPU models for QEMU 10.1.\n\n  * VM start: catch outdated Zen 5 CPU micro-code firmware that results in the\n    RDSEED CPU feature not being available for a VM and output a more telling\n    error message.\n\n  *  enroll-efi-keys: do not remove EFI disk when config was modified during\n     operation.\n\n  * config: apply pending: efi: enroll Microsoft UEFI CA 2023 when setting\n    ms-cert=2023 option to allow updating to the newer CA via API too.\n\n  * ovmf: also enroll the Windows UEFI CA 2023 key, which is separate from the\n    Microsoft key.\n\n  * migration: prohibit renaming special cloud-init drive on storage migration.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 20 Jan 2026 18:33:11 +0100\n\nqemu-server (9.1.3) trixie; urgency=medium\n\n  * fix #7092: fix regression with DBUS cleanup when migrating during node\n    shutdown\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 17 Dec 2025 15:11:20 +0100\n\nqemu-server (9.1.2) trixie; urgency=medium\n\n  * check efi vars: avoid warning about accessing an undefined value from Perl\n    when no OS type is set.\n\n  * query machine capabilities: remove left-over debug print.\n\n  * dbus-vmstate: fix method call on dbus object resolving to wrong instance.\n\n  * migrate: remove left-over dbus-vmstate instance when migrating without\n    conntrack state.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 10 Dec 2025 15:33:03 +0100\n\nqemu-server (9.1.1) trixie; urgency=medium\n\n  * fix #7044: migration: cap the auto-increased allowed-downtime-limit to the\n    maximum value that QEMU supports.\n\n  * qmp client: bump timeout for synchronous block-dev snapshot commands to\n    one hour, as they can take quite a bit of time, especially with qcow2\n    internal snapshots.\n\n  * qm cleanup: avoid potentially problematic cleanup during rollback.\n\n  * block-dev: add NBD: fix handling of IPv6 host to unbreak TCP disk live\n    migration for such setups in PVE 9.\n\n  * create: assume HA state 'started' when live-restoring a VM, to avoid that\n    the HA manager is stopping the VM during the restore due to not knowing\n    better.\n\n  * create: set, not add, HA resource request-state when restoring a VM that\n    is already a HA resource.\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 29 Nov 2025 23:26:50 +0100\n\nqemu-server (9.1.0) trixie; urgency=medium\n\n  * vm start: check efi vars: add missing newline in print.\n\n  * cpu config: nested-virt: update description of parameter to always\n    recommend a CPU model similar to the host.\n\n  * snapshot: prohibit live snapshot (remove) of qcow2 TPM drive on storage\n    with snapshot-as-volume-chain for the time being.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 19 Nov 2025 13:03:38 +0100\n\nqemu-server (9.0.30) trixie; urgency=medium\n\n  * api: add 'allow-ksm' to memory options that are allowed to be configured\n    by non-root users.\n\n  * vm start: ovmf: do not auto-enroll Microsoft UEFI CA 2023 for now, as this\n    triggers an active Bitlocker inside the VM and the admin doing the restart\n    of the VM might not have the Bitlocker recovery code available.\n    Instead print a informational message in the start task log for now.\n\n  * qm cli: add enroll-efi-keys command to update the efidisk for Windows VMs\n    that do not yet have the newer Microsoft UEFI CA 2023 enrolled.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 18 Nov 2025 15:10:24 +0100\n\nqemu-server (9.0.29) trixie; urgency=medium\n\n  * api: create vm: use ha-manager command to add VM as an HA resource, this\n    fixes adding a VM to HA when using the qm CLI tool directly.\n\n  * qmrestore CLI tool: allow starting a VM after it was restored successfully.\n\n  * qmrestore CLI tool: allow adding a VM as an HA resource after it was\n    restored.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 17 Nov 2025 22:31:09 +0100\n\nqemu-server (9.0.28) trixie; urgency=medium\n\n  * fix #6934: vm start: don't apply pending changes when resuming from\n    hibernation.\n\n  * Intel TDX: add support for configuring a quote-generation-socket to enable\n    a remote attestation flow.\n\n  * vm start: fix migration regression with Windows by only enrolling EFI\n    certs on cold start.\n\n  * qm import: use schema variant with 'import-from' disk allocation support.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 17 Nov 2025 17:14:55 +0100\n\nqemu-server (9.0.27) trixie; urgency=medium\n\n  * fix #6985: ovmf: auto-enroll newer Microsoft UEFI CA 2023 for existing\n    Windows 10 and 11, and Windows Server 2016, 2019, 2022 and 2025.\n\n  * cpu config: introduce vendor-agnostic 'nested-virt' CPU flag. The flag\n    will automatically resolve to the flag required for the current CPU vendor\n    of the host.\n    This flag should be combined with a vendor-specific vCPU type, e.g. like\n    EPYC-v4 or Cooperlake-v2, matching your host CPU vendor, otherwise nested\n    virtualization capabilities can still be limited inside the guest OS,\n    especially on Windows based VMs.\n\n  * close #5291: support disabling KSM for specific VMs through a config\n    memory option flag.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 14 Nov 2025 21:51:59 +0100\n\nqemu-server (9.0.26) trixie; urgency=medium\n\n  * fix #7014: fix regression starting aarch64 VMs with machine type >= 10.1.\n\n  * prepare fix for #6613: pass purge param to HA stack when removing VM.\n\n  * fix #5779: vm start: pass storage hints when activating guest volumes,\n    which, e.g., sets the rxbounce buffer option for Windows VMs on RBD\n    volumes.\n\n  * migration: offline volumes: drop deprecated special casing for TPM state.\n\n  * tpm: support non-raw volumes via FUSE exports for swtpm using the\n    qemu-storage-daemon.,\n\n  * fix #4693: drive: allow non-raw image formats for TPM state drive.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 14 Nov 2025 01:05:04 +0100\n\nqemu-server (9.0.25) trixie; urgency=medium\n\n  * pci: add 'driver' option for passthough of devices which cannot use the\n    default 'vfio-pci' driver.\n\n  * api: fix permission check for guest net device without bridge which was\n    accidentally restricted to root only.\n\n  * clone disk: fix handling of snapshot-as-volume-chain for EFI disks.\n\n  * status: rrddata: use fixed pve-vm-9.0 path.\n\n  * migration: conntrack: avoid crash when dbus-vmstate object cannot be added\n    or not added quickly enough.\n\n  * adapt secure virtualization code used for AMD SEV to prepare for Intel TDX.\n\n  * add initial Intel TDX support.\n\n  * query machine capabilities: only query features of matching CPU vendor.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 13 Nov 2025 12:00:45 +0100\n\nqemu-server (9.0.24) trixie; urgency=medium\n\n  * api: create/store: allow adding newly created VM to be managed as HA\n    resource.\n\n  * pending config: avoid warning about accessing an uninitialized value when\n    comparing new hotplug configuration with the existing one.\n\n  * improve performance of helper to query some config properties that is used\n    in a hot-path of the HA stack and can impact the performance of the\n    cluster resource scheduler when there are many VMs managed by HA.\n\n  * vm status: make sure disk{read, write} adhere to return schema to fix\n    temporary regression for PDM.\n\n  * config schema: document hugepages option.\n\n  * fix #6989: cloudinit: enable joliet ISO extension for the generated config\n    disks to improve compatibility with file names mandated by the nocloud ISO\n    image disk format and Windows guests.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 10 Nov 2025 17:29:52 +0100\n\nqemu-server (9.0.23) trixie; urgency=medium\n\n  * fix #6713: snapshot volume chain: fix snapshot after disk move with\n    zeroinit.\n\n  * api: dbus-vmstate: fix return property name in schema.\n\n  * vm status: also queue query-proxmox-support QMP commands to avoid stacking\n    timeouts.\n\n  * api: add missing snapshot info to get-config return schema.\n\n  * agent: implement fsfreeze helper to better handle lost commands to avoid\n    unnecessarily blocking the QMP socket for a prolonged time.\n\n  * migration: conntrack: work around systemd issue where scope for VM might\n    become blocked.\n\n  * fix #6828: remote migration: bump timeout for writing configuration to\n    accommodate volume activation. Allow for up to 2 minutes instead of just\n    10 seconds.\n\n  * fix #6882: backup provider api: fix backup with TPM state by correctly\n    generating node name.\n\n  * backup: fleecing: avoid warning when querying block node size for TPM\n    state.\n\n  * cfg2cmd: turn off the high-precision event timer (HPET) for Linux VMs\n    running at least kernel 2.6 and machine type >= 10.1, which will avoid\n    slightly increased CPU usage with newer QEMU versions. Further, Linux\n    kernel since v2.6.26+ (from July 2008!) can use the kvm-clock timer.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 03 Oct 2025 22:12:22 +0200\n\nqemu-server (9.0.22) trixie; urgency=medium\n\n  * partially fix #6805:\n    - api: modify vm config: privilege checks for VM-state-related\n      properties.\n    - api: clone: properly remove all snapshot-related info.\n\n  * api: create/update: disallow setting 'running-nets-host-mtu' via API.\n\n  * resume from suspended: properly handle 'nets-host-mtu'.\n\n  * vm command line: handle 'nets-host-mtu' property in snapshot.\n\n  * vm start: remove left-over VM-state-related properties.\n\n  * api schema: fix broken line continuation in the description for three\n    properties.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 17 Sep 2025 18:37:06 +0200\n\nqemu-server (9.0.21) trixie; urgency=medium\n\n  * fix #6608: expose vIOMMU driver address space bit width 'aw-bits' option.\n\n  * migration: tell users to upgrade the target node if nets-host-mtu is not\n    supported by the target qemu-server package version.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 10 Sep 2025 15:17:11 +0200\n\nqemu-server (9.0.20) trixie; urgency=medium\n\n  * api: rrd: add missing ds parameter for generating PNG graph.\n\n  * virtio-net: fix migration between default/non-default MTUs starting with\n    machine version 10.0+pve1.\n\n  * api: vm start: introduce nets-host-mtu parameter for migration compat.\n\n  * snapshot: introduce running-nets-host-mtu property.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 04 Sep 2025 19:49:27 +0200\n\nqemu-server (9.0.19) trixie; urgency=medium\n\n  * fix generating block-dev option on VM live import.\n\n  * fix #6680: do not use a top throttle node when using scsi-block, like for\n    passthrough of SCSI devices and 10+ machine version, as passed through\n    disks do not support bandwidth limitations.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 26 Aug 2025 09:35:28 +0200\n\nqemu-server (9.0.18) trixie; urgency=medium\n\n  * close #6378: expose guest-phys-bits CPU option to cope with using the host\n    CPU type on CPU models where the CPU address width and the IOMMU address\n    width are different. On such systems the guest-phys-bits option needs to\n    be set to properly support huge pages and/or VFIO.\n\n  * fix #6675: template backup: fix regression with IDE/SATA and blockdev.\n\n  * fix #6680: avoid setting nonexistent 'write-cache' and 'drive_id' options\n    for passed-through disks using SCSI with machine version >= 10.0.\n\n  * dbus-vmstate: fix installation path for script.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 14 Aug 2025 12:33:11 +0200\n\nqemu-server (9.0.17) trixie; urgency=medium\n\n  * fix #6648: api: available machine versions: ensure array of versions is\n    correctly ordered numerically.\n\n  * prohibit using snapshot-as-volume-chain qcow2 images with VMs that use a\n    pre-10.0 machine version.\n\n  * blockdev: attach/detach: silence errors for QMP commands for which failure\n    may be expected.\n\n  * blockdev: delete/replace: re-use common detach helper to avoid\n    false-positive errors in the syslog for devices that QEMU already\n    auto-removes itself.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 12 Aug 2025 15:10:38 +0200\n\nqemu-server (9.0.16) trixie; urgency=medium\n\n  * vmstate: always quiesce warnings on VM stop cleanup as at this point, the\n    dbus-vmstate helper is not expected to be running anymore, stopping it\n    here is just done out of precaution to ensure it will be cleaned up in any\n    case.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 05 Aug 2025 12:21:02 +0200\n\nqemu-server (9.0.15) trixie; urgency=medium\n\n  * vm state: improve cleaning up dbus-vmstate and avoid spurious warning if\n    guest gets shutdown from the inside.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 04 Aug 2025 16:02:28 +0200\n\nqemu-server (9.0.14) trixie; urgency=medium\n\n  * api: migration preconditions: fix default value for \"comigrated\" HA\n    resources,\n\n  * api: migration checks: rename return prop to dependent-ha-resources and\n    improve description.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 01 Aug 2025 18:37:51 +0200\n\nqemu-server (9.0.13) trixie; urgency=medium\n\n  * fix #6580: blockdev: commit: re-open target format node as writable\n    if necessary.\n\n  * blockdev: delete: delete format block node first.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 31 Jul 2025 14:26:03 +0200\n\nqemu-server (9.0.12) trixie; urgency=medium\n\n  * api: migration preconditions: add checks for ha resource affinity\n    rules\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 31 Jul 2025 11:33:45 +0200\n\nqemu-server (9.0.11) trixie; urgency=medium\n\n  * metrics: add pressure stall information to status.\n\n  * vm status: add memhost as accumulated PSS for all processes in a VMs cgroup\n    for complete host view of VM memory consumption.\n\n  * vm status: switch 'mem' stat to PSS of VM cgroup if there is no balloon\n    info.\n\n  * RRD metrics: use new pve-storage-9.0 format RRD file location, if it\n    exists.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 31 Jul 2025 04:50:09 +0200\n\nqemu-server (9.0.10) trixie; urgency=medium\n\n  * delete snapshot: exit delete early if we cannot find a snapshot\n\n  * api: add module exposing node migration capabilities\n\n  * fix #5180: dbus-vmstate: add daemon for QEMUs dbus-vmstate interface and\n    integrate helper for live-migrating connection-tracking info to better\n    ensure currently active connections can continue to work seamlessly if the\n    network environment supports it.\n\n  * migrate: flush old VM conntrack entries after successful migration.\n\n  * image convert (clone): avoid combining target image options and zeroinit\n    filter.\n\n  * image convert (clone): make using zeroinit with target-image-opts work.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 30 Jul 2025 23:06:51 +0200\n\nqemu-server (9.0.9) trixie; urgency=medium\n\n  * blockdev: ovmf: use correct cache mode for EFI disk to avoid slowness when\n    booting on certain storages like kRBD.\n\n  * migration: fix handling of status reported by QEMU to cover all relevant\n    cases with QEMU binary version 10.0 and avoid aborted migrations.\n\n  * disk mirror: avoid left-over block node attached to QEMU in error\n    scenario. Not detaching would also make subsequent live-migration fail after\n    an earlier error.\n\n  * snapshot-as-volume-chain: ensure backing file references are kept relative\n    upon snapshot deletion. This ensures the backing chain stays intact should\n    the volumes be moved to a different path.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Jul 2025 16:53:45 +0200\n\nqemu-server (9.0.8) trixie; urgency=medium\n\n  * fix #6562: fix online-removal of an intermediate snapshot for\n    directory-based storage types and snapshot-as-volume-chain.\n\n  * fix discard for blockdev backend, used by VMs with machine version 10 or\n    newer.\n\n  * fix #6543: use 'discard-no-unref' qcow2 option when creating snapshots as\n    volume backing-chain.\n\n  * device unplug: bump timeout for removing virtio scsi controller from 5\n    seconds to 15 seconds.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 25 Jul 2025 16:14:22 +0200\n\nqemu-server (9.0.7) trixie; urgency=medium\n\n  * drive device: fix regression with missing SCSI device ID and thus missing\n    '/dev/disk/by-id' paths inside the VM for Linux based guests.\n\n  * fix #6556: vm status: fix querying block stats.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Jul 2025 15:41:34 +0200\n\nqemu-server (9.0.6) trixie; urgency=medium\n\n  * use storage layer's helper to query the allowed formats and the best\n    possible default format for a specific storage config entry.\n\n  * blockdev: also set read-only flag on top throttle node, fixing backup of\n    VM templates with the recently adopted blockdev interface.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 22 Jul 2025 18:13:51 +0200\n\nqemu-server (9.0.5) trixie; urgency=medium\n\n  * ovmf: use proper drive properties for temporary efivars drive to avoid an\n    error from get_drive_id about no interface being found.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Jul 2025 21:37:10 +0200\n\nqemu-server (9.0.4) trixie; urgency=medium\n\n  * refuse storage-migration for guests with volume-chain-snapshots for now.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Jul 2025 14:09:34 +0200\n\nqemu-server (9.0.3) trixie; urgency=medium\n\n  * net: default to the bridge MTU for the MTU of virtio network devices\n\n  * api: agent: use more specific guest agent privileges\n\n  * api: monitor: improve permission handling\n\n  * api: monitor: require Sys.Audit or Sys.Modify privilege\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 17 Jul 2025 22:09:41 +0200\n\nqemu-server (9.0.2) trixie; urgency=medium\n\n  * fix #6400: pci: allow other pci domains than 0000 for NVIDIA vGPUs.\n\n  * fix #6466: aarch64: pci: properly print higher-index PCI bridge addresses.\n\n  * add initial support for storage managed external snapshots.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 17 Jul 2025 01:20:05 +0200\n\nqemu-server (9.0.1) trixie; urgency=medium\n\n  * cfg2cmd: require at least QEMU binary version 6.0\n\n  * drive: remove geometry options gone since QEMU 3.1\n\n  * vm start: assert that migration type is set for 'tcp' migration\n\n  * various code refactoring and cleanup, introducing:\n\n      BlockDev module\n      BlockJob module\n      Network module\n      OVMF module\n      QemuImage module\n      QemuMigrate::Helpers module\n      RunState module\n      StateFile module\n\n  * vm start/commandline: also clean up pci reservation when config_to_command() fails\n\n  * vm start/commandline: activate volumes before config_to_command()\n\n  * qm: showcmd: never reserve PCI devices\n\n  * fix #5985: qmp client: increase timeout for {device, netdev, object}_{add, del} commands\n\n  * qmp client: add default timeouts for more blockdev commands\n\n  * switch to blockdev and block-mirror with machine version >= 10.0\n\n  * blockdev: add workaround for issue #3229\n\n  * assume that SDN is available\n\n  * schema: remove unused pve-qm-ipconfig standard option\n\n  * move find_vmstate_storage() helper to QemuConfig module\n\n  * adopt perltidy also for executables without perl extension\n\n  * backup: use blockdev for fleecing images\n\n  * backup: use blockdev for TPM state file\n\n  * clone disk: skip check for aio=default (io_uring) compatibility starting with machine version 10.0\n\n  * test: migration: update running machine to 10.0\n\n  * partially fix #3227: ensure that target image for mirror has the same size for EFI disks\n\n  * blockdev: pass along machine version to storage layer\n\n  * net: use pve-firewall helper for deciding whether to create fw bridges\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 03 Jul 2025 13:40:14 +0200\n\nqemu-server (9.0.0) trixie; urgency=medium\n\n  * re-build for Debian 13 Trixie based Proxmox VE 9 releases.\n\n  * move module-load config from /etc to /usr, as a file with the same name in\n    the /etc/modules-load.d/ directory takes precedence over a file in the\n    /usr/lib/modules-load.d/ directory, an admin can still overrides this in a\n    easy manner.\n\n  * drop duplicated network hook script from /var, they have been migrated to\n    /usr/libexec since a while now, and as all users must upgrade to latest\n    PVE 8.4 before being able to upgrade to PVE 9 they are simply not required\n    anymore.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Jun 2025 15:27:09 +0200\n\nqemu-server (8.3.13) bookworm; urgency=medium\n\n  * fix #6317: backup: properly cleanup fleecing images after the backup of a\n    stopped VM.\n\n  * disk rescan: avoid adding fleecing images as unused disks.\n\n  * tests: add config-to-command tests for disk pass-through with RBD, kRBD\n    and ZFS over iSCSI storages.\n\n  * migrate: avoid warning about uninitialized value if transferred memory did\n    not change between iterations.\n\n  * tests: add qemu-img convert tests for disks on RBD and BTRFS storages with\n    snapshots.\n\n  * virtiofs: prevent issue with Windows OS and too many files. The number of\n    open file handles by the virtiofsd process would increase even if the\n    guest opened files sequentially. Use the 'inode-file-handles=prefer'\n    commandline option to avoid this.\n\n  * qmeventd: auto-format code base using clang-format.\n\n  * tests: add config-to-command tests for aio and cache settings on various\n    storages.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 16 Jun 2025 10:04:17 +0200\n\nqemu-server (8.3.12) bookworm; urgency=medium\n\n  * virtiofs: drop unsafe and problematic writeback option completely fow now.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 08 Apr 2025 17:30:49 +0200\n\nqemu-server (8.3.11) bookworm; urgency=medium\n\n  * handle various deprecations of options in upstream QEMU 9.2.\n\n  * parse config: skip unknown sections and warn about their presence.\n\n  * parse config: warn about duplicate sections.\n\n  * vzdump: skip all special sections inside guest config.\n\n  * fix #5440: vzdump: better cleanup fleecing images after hard errors using\n    a new \"fleecing\" special config section where the allocated fleecing\n    images get recorded. This record will be used to clean them up on any\n    error.\n\n  * migration: attempt to clean up potential left-over fleecing images.\n\n  * vm-network-scripts: move scripts to '/usr/libexec', keep old locations for\n    now to avoid a race condition on package upgrade.\n\n  * qmeventd: go back to extracting VMID from command line instead of cgroup\n    file to address sporadic errors when trying to query the VMID from an\n    exited process.\n\n  * fix #1027: add support for shared mounts using Virtiofs.\n\n  * block live-migration, snapshot (with RAM) and hibernation if any Virtiofs\n    device is configured.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 07 Apr 2025 23:35:50 +0200\n\nqemu-server (8.3.10) bookworm; urgency=medium\n\n  * drive: remove ancient and for a long time unsupported 'cow' from formats.\n\n  * confidential virtualization: add AMD SEV-SNP support.\n\n  * allow non-root users to add a virtion RNG (random number generator) device\n    using /dev/u?random or /dev/hwrng as source.\n\n  * config: add S3 and S4 power state properties to machine option.\n\n  * disable S3 and S4 power states for new machine versions, these states are\n    almost never useful for virtual machines and can even cause (hibernation\n    or suspend related) problems.\n\n  * api: qemu machine capabilities: return custom pveX versions and include\n    core changes for them in a new property.\n\n  * backup: allow adding fleecing images also for EFI and TPM\n\n  * backup: keep track of block-node size for fleecing to avoid problems with,\n    e.g., efidisks on some storage types.\n\n  * backup: implement backup and restore operations for external provider\n    storage plugins.\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 06 Apr 2025 21:39:19 +0200\n\nqemu-server (8.3.9) bookworm; urgency=medium\n\n  * fix #5284: api: move-disk, clone: assert content type support for target\n    storage to avoid situations where one can clone or move a disk to a\n    storage where it then cannot be used anymore, blocking VM start.\n\n  * disallow deleting the tpmstate and efidisk from configuration while the VM\n    is running to avoid an inconsistent state.\n\n  * api: termproxy/vncproxy: fix 'use of uninitialized value' warning when\n    checking vga type\n\n  * migration: extend clean-up of resources after successful migration, like\n    freeing up PCI mdev usage again.\n\n  * migration blockers: allow mapped devices for offline migration as long as\n    the mapping is also available on the target node.\n\n  * pci passthrough: enable live-migration for PCI passthrough device that are\n    handled by a resource mapping that declared support for live-migration.\n\n  * api: migration preconditions: return detailed info for mapped-resources\n    and deprecated previously returned flat list, it will be removed in a\n    future major release.\n\n  * api: migrate preconditions: include not available mapped resources also\n    for running VMs to improve UX.\n\n  * migration: show amount of traffic for transferring vfio state in progress\n    log.\n\n  * migration: report short summary for transfer traffic stats once migration\n    finished.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 03 Apr 2025 17:43:04 +0200\n\nqemu-server (8.3.8) bookworm; urgency=medium\n\n  * resume: error out if VM is a template\n\n  * backup: also restore VM power state for early failures\n\n  * config: add special class that prevents writing the configuration\n\n  * fix #6007: template backup: use minimized configuration for handling the\n    full vm start\n\n  * vzdump: align behavior for vma backup with PBS backup for templates\n\n  * config: make attempts at writing out NoWrite configs fatal\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 18 Feb 2025 14:59:25 +0100\n\nqemu-server (8.3.7) bookworm; urgency=medium\n\n  * api: fix ova live import of disks by using correct format for source image\n\n  * qmp helpers: use the HMP interface as a stopgap to deal with QEMU 9.2's\n    stricter requirements for the serialized format sent over the QMP.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 04 Feb 2025 17:12:25 +0100\n\nqemu-server (8.3.6) bookworm; urgency=medium\n\n  * api: do not set an empty machine option when pinning\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Jan 2025 14:45:58 +0100\n\nqemu-server (8.3.5) bookworm; urgency=medium\n\n  * unify CDROM medium hotplug and startup code\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Jan 2025 11:21:50 +0100\n\nqemu-server (8.3.4) bookworm; urgency=medium\n\n  * vm shutdown: check if QEMU Guest Agent (QGA) is actually running before\n    relying on it for handling the shutdown. This avoids waiting for the\n    timeout just to then fail the task.\n\n  * api: VM status: document 'cpu' and 'mem' return types\n\n  * api: create disks: also convert the 'tpmstate' and 'efidisk0' volumes to\n    base image for VM templates\n\n  * machine: fallback to QEMU version from the VM creation time for windows\n    VMs that get started with QEMU version 9.1 or later, if there is no\n    explicit version set.\n\n  * api: update vm config: pin machine version when switching to Windows os\n    type, just like we do since a long time for newly created VMs with a\n    Windows OS type.\n\n  * machine: log informational line when pinning machine version for Windows\n    guest\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 17 Jan 2025 19:32:17 +0100\n\nqemu-server (8.3.3) bookworm; urgency=medium\n\n  * fix #5980: import disk: fix spurious warning if no target disk is defined\n\n  * api: clone: always do a full clone of tpmstate volumes\n\n  * swtpm: check that format of tpmstate volume is raw as swtpm currently\n    doesn't support anything else anyway.\n\n  * create disk: disallow adding existing non-raw volumes as tpmstate0, like\n    as for new volumes.\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 15 Dec 2024 14:26:32 +0100\n\nqemu-server (8.3.2) bookworm; urgency=medium\n\n  * use image format from storage layer for PVE-managed volumes\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 09 Dec 2024 10:08:14 +0100\n\nqemu-server (8.3.1) bookworm; urgency=medium\n\n  * api: clone vm: make error for unsupported volumes appear in a\n    deterministic order and include the ID of the problematic volume.\n\n  * fix #3588: helper: consider NIC count for config-specific timeout\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 05 Dec 2024 12:38:12 +0100\n\nqemu-server (8.3.0) bookworm; urgency=medium\n\n  * api: import working storage: improve error message\n\n  * migration: drop outdated check that was introduced for PVE 7.2 to PVE 7.3\n    to guard against (relatively low) fallout of the newly introduced\n    cloudinit section in the VM config. This check can sometimes trigger as\n    false-positive.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 20 Nov 2024 12:12:00 +0100\n\nqemu-server (8.2.7) bookworm; urgency=medium\n\n  * backup: cleanup: check if VM is running before issuing QMP commands\n\n  * restore: die early when there is no size for a device\n\n  * add tiny C program to query some hardware capabilities from CPUID\n\n  * backup: prepare: cancel previous job if still running, which can happen\n    after a hard failure, e.g. if the vzdump task was canceled.\n\n  * parse config: allow config keys with minus sign\n\n  * import disk: add 'target-disk' option to add imported volume to disk\n\n  * import disk: convert imported volume disks to base images for templates\n\n  * fix #5301: convert added volume disks to base image for templates\n\n  * use OVF module from the pve-storage package, it was moved over there\n\n  * api: create: implement extracting disks when needed for import-from\n\n  * api: create: add 'import-working-storage' parameter for overriding the\n    staging directory used to extract to-be-imported disks.\n\n  * api: check untrusted image files for the new import content type for\n    proactive hardening.\n\n  * config: add initial AMD SEV support\n\n  * migration: add amd-sev to non-migratable resources blockers\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 18 Nov 2024 21:48:24 +0100\n\nqemu-server (8.2.6) bookworm; urgency=medium\n\n  * vm start: add syslog info with which PID a VM process was started\n\n  * pci: avoid the hard requirement for a passthrough device to be reset on\n    start to fix a recent regression from improving error handling from the\n    pve-common package.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 11 Nov 2024 20:38:25 +0100\n\nqemu-server (8.2.5) bookworm; urgency=medium\n\n  * remote migration: fix online migration via API clients\n\n  * fix #5714: fix calloc parameter ordering\n\n  * fix typos in user-visible strings\n\n  * api: status: add some missing description for status return properties\n\n  * pci: device selection: don't reserve PCI IDs when VM is already running to\n    avoid false-positive if, e.g., one is checking the generated QEMU command\n    using `qm showcmd`.\n\n  * pci: mdev: adapt to NVIDIA's modern interface with kernel >= 6.8\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 24 Oct 2024 18:55:37 +0200\n\nqemu-server (8.2.4) bookworm; urgency=medium\n\n  * fix #5528: ensure cgroup limits like CPU weight are set correctly and in\n    the same way for both cases, the cold-start and when changing while the VM\n    is running\n\n  * fix #5619: honor link-down setting when hot-plugging NIC\n\n  * resume: bump timeout for query-status, as activating the block drives in\n    QEMU on resume can take a bit of time during which the QEMU process might\n    not respond to QMP commands yet.\n\n  * fix #4493: cloud-init: fix generated Windows config\n\n  * drive mirror: prevent wrongly logging success when completion fails for\n    some other reasons than for which it's safe to retry on.\n\n  * migration: avoid crash with heavy IO on migrating a disk backed by local\n    storage, if the VM is running QEMU 8.2 or newer.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 30 Jul 2024 21:36:25 +0200\n\nqemu-server (8.2.3) bookworm; urgency=medium\n\n  * config: net: avoid duplicate ipam entries on nic update\n\n  * fix #5574: api: fix permission check for 'spice' usb port\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 22 Jul 2024 19:42:15 +0200\n\nqemu-server (8.2.2) bookworm; urgency=medium\n\n  * schema: mention that migrate_downtime parameter will be auto-increased if\n    migration cannot converge otherwise.\n\n  * remote migration: enable schema validation\n\n  * suspend: also cleanup in error scenario when QMP command 'savevm-end'\n    fails rather than leaving orphaned volume around and VM configuration\n    locked.\n\n  * human monitor commands: increase timeout to sensible values. In particular\n    to fix #5440 where detaching a fleecing image would fail, because of the\n    low timeout.\n\n  * vma restore: increase timeout for reading header and improve error\n    messages.\n\n  * fix #5562: improve TPM version handling by disallowing change in the\n    configuration after creation of the state file (the actual state file\n    cannot change version either). Also avoid warning about undefined value\n    when version is not explicitly set and fix schema to mention the actual\n    default for the version, being 'v1.2'.\n\n  * fix #3352: minimize config when starting templates for PBS backup. A\n    template might reside on a host that does not have the necessary resources\n    or devices to start the VM with all settings.\n\n  * block jobs: improve error handling and messages for drive mirror and\n    live-restore/live-import.\n\n  * fix #5572: avoid warning about uninitialized value when cloning cloud-init\n    disk.\n\n  * CLI autocomplete: also list backup archives from PBS storages and without\n    compressor extension.\n\n  * remove outdated QEMU version checks working around missing features in\n    QEMU binaries 4.2 and 4.0.1.\n\n  * vga: mention that type 'cirrus' is recommended against.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 12 Jul 2024 16:09:25 +0200\n\nqemu-server (8.2.1) bookworm; urgency=medium\n\n  * cpu config: fix get_cpu_bitness always reverting to default cpu type\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 24 Apr 2024 11:49:03 +0200\n\nqemu-server (8.2.0) bookworm; urgency=medium\n\n  * qmeventd: also treat 'prelaunch' and 'suspended' states as active to avoid\n    issues when backing up VMs that currently are in those states.\n\n  * OS type: add Windows Server 2025 as supported, map it to the same virtual\n    hardware profile as the Windows 11 one.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 23 Apr 2024 17:09:20 +0200\n\nqemu-server (8.1.4) bookworm; urgency=medium\n\n  * api: create vm: add missing import for serializing machine type to fix a\n    regression of version 8.1.2.\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 20 Apr 2024 12:28:35 +0200\n\nqemu-server (8.1.3) bookworm; urgency=medium\n\n  * firewall: add handling for new nftables based firewall implementation,\n    which currently is a opt-in drop-in replacement for the older iptables-\n    based one.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 19 Apr 2024 20:23:39 +0200\n\nqemu-server (8.1.2) bookworm; urgency=medium\n\n  * fix #4136: backup: implement fleecing option for improved guest\n    stability when the backup target is slow\n\n  * fix #4474: stop VM: add 'overrule-shutdown' parameter to prevent\n    currently running shutdown tasks from blocking the stop task\n\n  * fix #1905: disk move: allow moving unused disks\n\n  * fix #3784: add vIOMMU parameter to support passthrough of PCI devices to\n    nested virtual machines\n\n  * fix #5363: cloudinit: fix regression to make creation of scsi cloudinit\n    disks possible again\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 19 Apr 2024 16:09:18 +0200\n\nqemu-server (8.1.1) bookworm; urgency=medium\n\n  * config: pending network: avoid undef-warning on old/new comparison\n\n  * support live-import for 'import-from' disk options on create\n\n  * qm: add 'import' command for importing a VM through a volumeid from a\n    storage that provides the new 'import' content-type.\n\n  * disk import: warn when fallback is used instead of requested format\n\n  * cpu config: die on hotplug of non x86_64 CPUs\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 14 Mar 2024 14:04:34 +0100\n\nqemu-server (8.1.0) bookworm; urgency=medium\n\n  * migration: do not allow live-migration with VNC clipboard, it's not yet\n    supported by QEMU's vdagent device, which gets used for this feature.\n\n  * cpu config: add QEMU 8.1 cpu models\n\n  * fix #4501: TCP migration: start vm: move port reservation and usage closer\n    together\n\n  * fix #2258: select correct device when removing drive snapshot via QEMU, as\n    the device where the disk is currently attached needs to be targeted, not\n    the one where the disk was attached at the time the snapshot was taken\n\n  * fix #4957: allow one to set the vendor and product information for\n    SCSI-Disks explicitly\n\n  * migration: remember original volume names from the source so that they can\n    get deactivated even if the volume name has to change due to being already\n    in use on the target storage.\n\n  * fix #4085: properly activate the storage(s) of custom cloud-init snippets\n\n  * fix #1734: clone VM: if deactivation of source volume fails demote error\n    to warning. Deactivation can fail if the source is still, or again, in\n    use, e.g., due to parallel clones of the same template.\n\n  * mediated device pass-through: avoid race condition for cleaning up on VM\n    reboot\n\n  * prevent starting a 32-bit VM using a 64-bit OVMF BIOS\n\n  * QMP client: increase default timeout for drive-mirror to 10 minutes like\n    for other block operations.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 08 Mar 2024 15:00:25 +0100\n\nqemu-server (8.0.10) bookworm; urgency=medium\n\n  * sdn: pass vmid and hostname to allow requesting a new mapping\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Nov 2023 14:12:46 +0100\n\nqemu-server (8.0.9) bookworm; urgency=medium\n\n  * add clipboard option to to vga config entry\n\n  * api: add clipboard variable to return at status/current\n\n  * recommend libpve-network-perl for SDN support\n\n  * initial support for dhcp ip allocation in dhcp-enabled SDN zones\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 21 Nov 2023 15:40:27 +0100\n\nqemu-server (8.0.8) bookworm; urgency=medium\n\n  * fix #2816: restore: remove timeout when allocating disks\n\n  * start: increase maximal timeout if using PCI passthrough\n\n  * fix #4522: api: vncproxy: also set environment variable for the ticket\n    if the websocket option is not set\n\n  * backup, migrate: fix races with suspended VMs that can wake up\n\n  * cpu hotplug: cannot change feature online, so keep these as pending change\n\n  * nbd-stop: increase timeout to 25s\n\n  * start: add warning if a deprecated machine version is configured\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 12 Nov 2023 18:54:37 +0100\n\nqemu-server (8.0.7) bookworm; urgency=medium\n\n  * fix #4822: vzdump: fix PBS encryption for guests without disks\n\n  * qmeventd: fix parsing of VMID in presence of legacy cgroup entries\n\n  * api: check access for already configured bridge when updating vNIC\n\n  * fix #4620: make 'ide1' and 'ide3' drive keys work for machine type q35\n\n  * cloudinit: fix two checks that were mistakenly restricted to root only,\n    one for setting the ciupgrade option and one for updating the cloudinit\n    drive\n\n  * migration: improve format hint when allocating live-migrated disk on the\n    target to make e.g. remote-migration with qcow2 and LVM-thin target work\n\n  * net: fix setting value for tx_queue_size\n\n  * fix #3963: allow backup of template VM with immutable TPM drive\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Aug 2023 11:30:45 +0200\n\nqemu-server (8.0.6) bookworm; urgency=medium\n\n  * cloudinit: restore previous default for package upgrades\n\n  * migration: only migrate disks used by the guest, not also those that are\n    owned by them (VMID in name) but not referenced in the config\n\n  * migration: fail when aliased volume are detected, as referencing the same\n    volume multiple times can lead to unexpected behavior in a migration.\n\n  * migration: fix issue with qcow2 cloudinit disk live migration\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 21 Jun 2023 13:03:01 +0200\n\nqemu-server (8.0.5) bookworm; urgency=medium\n\n  * restore: extend permissions checks\n\n  * vm start: always reset any failed-state of the VM systemd scope to avoid\n    failing a re-start after, e.g., a OOM kill.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 21 Jun 2023 09:17:41 +0200\n\nqemu-server (8.0.4) bookworm; urgency=medium\n\n  * vCPU config: add new x86-64-v2, x86-64-v3 and x86-64-v4 models\n\n  * fix #4784: helpers: cope with native versions in manager version check\n\n  * enable cluster mapped USB devices for guests\n\n  * enable cluster mapped PCI devices for guests\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 19 Jun 2023 07:24:11 +0200\n\nqemu-server (8.0.3) bookworm; urgency=medium\n\n  * qemu: fix permission check call\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 09 Jun 2023 12:20:40 +0200\n\nqemu-server (8.0.2) bookworm; urgency=medium\n\n  * cfg2cmd: use actual backend names instead of removed tty and paraport\n    aliases\n\n  * cfg2cmd: replace deprecated no-acpi option with acpi=off machine flag\n\n  * cfg2cmd: replace deprecated no-hpet option with hpet=off machine flag\n\n  * schema: avoid using deprecated -no-hpet in example for 'args' property,\n    instead pass thate via new machine option\n\n  * allow setting ipconfigX with VM.Config.Cloudinit\n\n  * fix #3428: cloudinit: add parameter for upgrade on boot\n\n  * cloudinit: fix 'pending' api endpoint\n\n  * fast plug options: add migrate_downtime and migrate_speed for convenience\n\n  * fix #517: api: allow resizing qcow2 disk with snapshots\n\n  * fix #2315: api: have resize endpoint spawn a worker task\n\n  * cloudinit: pass through hostname via fqdn field\n\n  * qmeventd: extract vmid from cgroup file instead of cmdline\n\n  * config: implement method to calculate derived properties from a config\n\n  * api: check bridge access for create, update, clone & restore\n\n  * qm: remote migration: improve error when storage cannot be found\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 09 Jun 2023 10:26:19 +0200\n\nqemu-server (8.0.1) bookworm; urgency=medium\n\n  * fix #4737: qmeventd: gracefully handle interrupted epoll_wait call\n\n  * handle and warn about VM network interfaces not attached to any bridges\n\n  * block resize: avoid passing zero size to QMP command\n\n  * qmrestore: improve description of bwlimit parameter\n\n  * api: switch agent api call to 'array' type\n\n  * tests: fix invoking migration tests with make\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 07 Jun 2023 13:50:09 +0200\n\nqemu-server (8.0.0) bookworm; urgency=medium\n\n  * never enable 'smm' flag for the 'virt' machine type (doesn't exist)\n\n  * test: mock calls that can fail in a chroot environment\n\n  * rebuild for Debian Bookworm based releases\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 19 May 2023 15:07:45 +0200\n\nqemu-server (7.4-3) bullseye; urgency=medium\n\n  * backup prepare: fix format detection for disks without storage ID\n\n  * backup prepare: improve error messages\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 27 Mar 2023 11:17:16 +0200\n\nqemu-server (7.4-2) bullseye; urgency=medium\n\n  * avoid list context for volume_size_info calls as otherwise we\n    unnecessarily take a slower code path\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 21 Mar 2023 16:51:01 +0100\n\nqemu-server (7.4-1) bullseye; urgency=medium\n\n  * fix #4553: nvidia vgpu: reuse smbios uuid for '-uuid' parameter\n\n  * pci: workaround nvidia driver issue on mdev cleanup\n\n  * memory: hotplug: sort by numerical ID rather than slot when unplugging\n\n  * memory: use the DIMM list info from QEMU for unplug\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Mar 2023 17:24:45 +0100\n\nqemu-server (7.3-4) bullseye; urgency=medium\n\n  * fix #4378: standardized error for missing OVMF files\n\n  * schema: memory: be precise that unit is binary prefix\n\n  * close #2792: allow online migration with replicated snapshots\n\n  * schema: OS type: note that the l26 type is also compatible with Linux 6.x\n\n  * hotplug: disk: mark the 'aio' (async IO) as non-hotpluggable to avoid\n    suggesting that it already changed\n\n  * fix #4525: clone disk: disallow mirror if it might cause problems with\n    io_uring using the same heuristics as for start-up\n\n  * start: make not being able to set polling interval for ballooning\n    non-critical\n\n  * swtpm: enable logging to `/run/qemu-server/$vmid-swtpm.log`\n\n  * fix #4140: vzdump: transform the previous hardcoded behavior of issuing a\n    fs-freeze and fs-thaw if QGA is enabled  into an overridable option named\n    'fs-freeze-on-backup'\n\n  * update network dev: MTU is not hot-pluggable, avoid suggesting so\n\n  * fix #4249: make image clone or conversion respect bandwidth limit\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 23 Feb 2023 17:12:42 +0100\n\nqemu-server (7.3-3) bullseye; urgency=medium\n\n  * rollback: ignore auto-start request if VM is already running\n\n  * memory hot-plug: check correct value for maximal memory check\n\n  * fix #4435: device list: avoid error for undefined value\n\n  * fix #4358: ignore any suspended lock when destroying a VM\n\n  * migration: log error from query-migrate, if any, upon migration failure\n\n  * cd rom handling: return a clearer error when there is no CD-ROM drive\n\n  * migration: nbd export: switch away from deprecated QMP command\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 16 Jan 2023 13:52:30 +0100\n\nqemu-server (7.3-2) bullseye; urgency=medium\n\n  * fix #4372: improve edge-case for config-loading on VM resume when\n    migrating\n\n  * ovmf cmd assembly: re-work and re-order arguments assembly\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 16 Dec 2022 12:54:53 +0100\n\nqemu-server (7.3-1) bullseye; urgency=medium\n\n  * vm resume: improve loading just recently moved config on nocheck/migrate\n    handling\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Nov 2022 13:43:59 +0100\n\nqemu-server (7.2-12) bullseye; urgency=medium\n\n  * config: only save unique tags when updating them via the API\n\n  * api: create/update vm: fix clamping CPU units function calls\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Nov 2022 08:36:06 +0100\n\nqemu-server (7.2-11) bullseye; urgency=medium\n\n  * fdb: only manage FDB entries for Linux bridges, ignore OVS for now\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 20 Nov 2022 16:30:28 +0100\n\nqemu-server (7.2-10) bullseye; urgency=medium\n\n  * fix #4321: properly check cloud-init drive permissions, require both\n    VM.Config.CDROM and VM.Config.Cloudinit, and not VM.Config.Disk, for being\n    able to add a cloud init drive in the first place.\n\n  * api: config update: enforce new tag permission system when setting or\n    removing tags from a guest\n\n\n  * parse config: do not validate informative values in cloud init section\n\n  * fix edge-cases on new cloudinit pending/active recording\n\n  * mtunnel: add API endpoints\n\n  * migrate: add foundation for remote (external cluster) migration, add\n    respective endpoints and qm `remote-migrate` CLI command\n\n  * memory hotplug: make max-memory dynamically calculated from the physicall\n    address bits the VM will use, that is the actual one from the config, if\n    set, the one from the host for CPU type host and 40 bits as fallback for\n    everything else. Calculate the addressable memory (e.g., 40 bits = 1 TiB)\n    and half that for the possible max-memory a VM can use, using the previous\n    hard-coded 4 TiB as overall maximum for backward compat.\n    Admins with inhomogeneous CPUs and thus possible different bit-widths need\n    to take special care themselves to ensure that a VM with memory hot-plug\n    configured can run on other nodes, for example for live-migration.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 17 Nov 2022 17:48:03 +0100\n\nqemu-server (7.2-8) bullseye; urgency=medium\n\n  * fix #4296: virtio-net: enable packed queues for qemu 7.1\n\n  * virtio-net: increase defaults rx- and tx-queue-size to 1024\n\n  * fix #4296: virtio-net: enable packed queues for QEMU machines using 7.1 or\n    newer\n\n  * net: increase max queues to 64\n\n  * fix #4284: add read-only to non-hotpluggable disk options\n\n  * delay cloudinit generation in hotplug\n\n  * record cloud-init changes in the cloudinit section\n\n  * rework cloudint config pending handling\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 16 Nov 2022 18:23:39 +0100\n\nqemu-server (7.2-7) bullseye; urgency=medium\n\n  * api: create/update vm: automatically clamp cpuunit value depending of\n    cgroup version\n\n  * improve cloud init support and add cloudinit hotplug\n\n  * vzdump: skip `special:cloudinit` section\n\n  * fix #3890 - GUI: warn for unlikely iothread config clashes\n\n  * fix #4228: add `start` parameter to snapshot rollback API so that one can\n    automatocally start the VM after rollback finished.\n\n  * vm start/stop: cleanup passed-through pci devices in more situations\n\n  * fix #3593: allow one to configure task set affinity for VMs\n\n  * fix #4324: USB: use qemu-xhci for machine versions >= 7.1\n\n  * usb: increase max USB devices from 5 to 14 for modern 7.1 machine\n    and OS versions (Linux 2.6+ and Windows 8+)\n\n  * fix #4201: delete cloud-init disk on rollback\n\n  * net devs: register vNIC MAC-Address manually to FDB on start/resume if\n    bridge has learning disabled\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 13 Nov 2022 15:46:18 +0100\n\nqemu-server (7.2-6) bullseye; urgency=medium\n\n  * schema: move 'pve-targetstorage' to pve-common\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 07 Nov 2022 16:22:50 +0100\n\nqemu-server (7.2-5) bullseye; urgency=medium\n\n  * qmp client: increase guest fstrim timeout to 10 minutes\n\n  * fix #3577: prevent suspension for VMs with pci passthrough\n\n  * cpu config: map deprecated IceLake-Client CPU type to IceLake-Server\n\n  * snapshot: save VM state: propagate error from QEMU\n\n  * api: create disks: avoid adding secondary cloud-init drives\n\n  * vzdump: TPM state: escape drive string\n\n  * qmp client: increase default fallback timeout to 5s\n\n  * fix regex matching network devices in qm cleanup so that vNICs with double\n    digit IDs are covered too\n\n  * qmeventd: rework 'forced_cleanup' handling and set timeout to 60s\n\n  * qmeventd: send QMP 'quit' command instead of SIGTERM\n\n  * vzdump: set max-workers QMP option when specified and supported\n\n  * fix #4099: disable io_uring for virtual disks on CIFS storages for now\n\n  * qm: move VM-disk related commands to own command group, keep old ones\n    around for backward compatibility\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 07 Nov 2022 16:15:16 +0100\n\nqemu-server (7.2-4) bullseye; urgency=medium\n\n  * fix #3754: encode JSON as utf8 for CLI\n\n  * cpuconfig: add amd epyc milan model\n\n  * fix #4115: enable option to name QEMU threads after their main purpose\n\n  * fix #4119: give namespace parameter to live-restore\n\n  * automatically add 'uuid' parameter when passing through NVIDIA vGPU\n\n  * vzdump/pbs: die with missing, but configured encryption key\n\n  * vzdump/pbs: die with missing, but configured master key\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 16 Aug 2022 13:59:20 +0200\n\nqemu-server (7.2-3) bullseye; urgency=medium\n\n  * support pbs namespaces\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 12 May 2022 15:14:39 +0200\n\nqemu-server (7.2-2) bullseye; urgency=medium\n\n  * api: reassign disk: drop moved disk from boot order\n\n  * explicitly check some prerequisites for virtio-gl display\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 02 May 2022 17:26:16 +0200\n\nqemu-server (7.2-1) bullseye; urgency=medium\n\n  * migrate: add log for guest fstrim and make a failure noticeable\n\n  * migrate: resume initially running VM when failing after convergence\n\n  * parse vm config: remove \"\\s*\" from multi-line comment regex\n\n  * memory: enable balloon free-page-reporting for auto-memory reclaim\n\n  * enable spice also for virtio-gl and virtio-gpu displays and report so in\n    status API\n\n  * api: create: allow overriding non-disk options during restore\n\n  * fix #3861: migrate: fix live migration when cloud-init changes storage\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 28 Apr 2022 18:35:22 +0200\n\nqemu-server (7.1-5) bullseye; urgency=medium\n\n  * avoid writing the config if there are no pending changes to apply\n\n  * fix #3792: cloudinit: use of uninitialized value\n\n  * pci: allow override of PCI vendor/device ids\n\n  * drive mirror monitor: warn when suspend/resume/freeze/thaw calls fail\n\n  * update config: allow setting boot-order and dev in one go\n\n  * migrate: move tunnel-helpers to pve-guest-common\n\n  * fix #3683: agent file-write: enable user to encode the content themselves\n\n  * cpu units: lower minimum for accessing full cgroupv2 range\n\n  * fix #3845: also clamp cpu units to cgroup dependent valid range on hotplug\n\n  * clone disk: force raw format for TPM state\n\n  * fix #3886: QEMU restore: verify storage allows images before writing\n\n  * fix #3733: bump the timeout used to wait that a for backup started VM is\n    fully stopped (i.e., it's \"$vmid.scope vanished) to 20 seconds after the\n    backup has finished to\n\n  * qmp client: increase timeout for thaw to better accommodate the QGA running\n    in Windows VMs\n\n  * api: vm start: 'force-cpu' is for internal migration use only, mark as\n    such\n\n  * device unplug: verify that unplugging SCSI disk completed before\n    continuing with remaining unplug work.\n\n  * clone disk: remove ancient check for min QEMU version 2.7\n\n  * clone disk: pass in efi vars size rather than config\n\n  * clone disk: allow cloning from an unused or unreferenced disk\n\n  * parse ovf: untaint path when getting the file's size info\n\n  * image convert: allow block device as source\n\n  * fix #3424: api: snapshot delete: wait for active replication\n\n  * PCI: allow longer pci domains\n\n  * fix #3957: spell 'occurred' correctly\n\n  * clone disk: also clone EFI disk from snapshot\n\n  * api: add endpoint for parsing .ovf files\n\n  * api: support VM disk import\n\n  * migrate: keep VM paused after migration if it was before\n\n  * vga: add virtio-gl display type for VIRGL\n\n  * restore: cleanup oldconf: also clean up snapshots from kept volumes\n\n  * restore: also deactivate/destroy cloud-init disk upon error\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 25 Apr 2022 20:15:59 +0200\n\nqemu-server (7.1-4) bullseye; urgency=medium\n\n  * migrate: send updated TPM state volume ID to target node on local-storage\n    migration\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 22 Nov 2021 17:07:13 +0100\n\nqemu-server (7.1-3) bullseye; urgency=medium\n\n  * replication: do not setup NBD server on VM migrate for the TPM state,\n    QEMU cannot access it directly and we already migrate it via the non-QEMU\n    storage migration anyway.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 16 Nov 2021 14:04:45 +0100\n\nqemu-server (7.1-2) bullseye; urgency=medium\n\n  * cfg2cmd: disable SMM when display=none and SeaBIOS is both used\n\n  * pci: do not reserve pci-ids for mediated devices, already handled by sysfs\n    anyway\n\n  * exclude efidisk and tpmstate for boot disk selection heuristic\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 15 Nov 2021 16:59:23 +0100\n\nqemu-server (7.0-19) bullseye; urgency=medium\n\n  * rollback: improve interaction with snapshot replication\n\n  * cli: qm: rename 'move_disk' command to 'move-disk' with an alias for\n    backward compatibility\n\n  * pi: move-disk: add possibility to reassign a disk to another VM\n\n  * turn SMM off when SeaBIOS and a serial-display are used in combination to\n    avoid a possible boot loop\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 11 Nov 2021 12:49:10 +0100\n\nqemu-server (7.0-18) bullseye; urgency=medium\n\n  * use non SMM ovmf code file for i440fx machines\n\n  * fix hot-unplugging (removing) a cpulimit on a running VM\n\n  * vm start: only print tpm-related message if there is an actual instance\n\n  * vzdump: increase timeout for QMP 'cont' command after backup started\n\n  * drives: expose readonly flag for SCSI/VirtIO drives as 'ro' property\n\n  * qemu-agent: allow hotplug of the 'fstrim cloned disk' option\n\n  * fix #2429: allow to specify cloud-init vendor snippet via 'cicustom'\n\n  * config: add new meta property with the VM creation time\n\n  * config: meta: also save the QEMU version installed during creation\n\n  * cfg2cmd: switch off ACPI hotplug on bridges for q35 VMs with linux as\n    ostype to avoid changes in network interface naming due to systemd's\n    predictable naming scheme\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 04 Nov 2021 15:29:55 +0100\n\nqemu-server (7.0-17) bullseye; urgency=medium\n\n  * fix #3258: block vm start when a PCI(e) device is already in use\n\n  * snapshot: fix TPM state with RBD\n\n  * swtpm: wait for PID file to appear before continuing with VM start\n\n  * OS type: add entry for Windows 11/Server 2022\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 21 Oct 2021 11:57:09 +0200\n\nqemu-server (7.0-16) bullseye; urgency=medium\n\n  * ovmf: support secure boot enabled code images\n\n  * ovmf: support provisioning an EFI vars template with secureboot by default\n    on and distribution + Microsofts secure-boot key pre-enrolled\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 05 Oct 2021 20:22:18 +0200\n\nqemu-server (7.0-15) bullseye; urgency=medium\n\n  * api: return task-worker UPID in create template endpoint\n\n  * api: destroy VM: remove pending volumes as well\n\n  * fix #3075: add TPM v1.2 and v2.0 support via swtpm~\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 05 Oct 2021 07:24:52 +0200\n\nqemu-server (7.0-14) bullseye; urgency=medium\n\n  * fix #3581: pass size via argument for memory-backend-ram QMP call\n\n  * fix #3608: improve removal of the underlying SCSI controller when removing\n    last drive on it\n\n  * migrate: do not suggest that we map shared storages to avoid that\n    subsequent checks could result in false negatives.\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Sep 2021 09:31:06 +0200\n\nqemu-server (7.0-13) bullseye; urgency=medium\n\n  * fix bootorder regression with implicit default order\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 5 Aug 2021 14:03:14 +0200\n\nqemu-server (7.0-12) bullseye; urgency=medium\n\n  * fix #3371: import ovf: allow the use of dots in the VM name\n\n  * bootorder: fix double entry on cdrom edit\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 30 Jul 2021 16:53:44 +0200\n\nqemu-server (7.0-11) bullseye; urgency=medium\n\n  * nic: support the intel e1000e model\n\n  * lvm: avoid the use of io_uring for now\n\n  * live-restore: fail early if target storage doesn't exist\n\n  * api: always add new CD drives to bootorder\n\n  * fix #2563: allow live migration with local cloud-init disk\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Jul 2021 11:08:48 +0200\n\nqemu-server (7.0-10) bullseye; urgency=medium\n\n  * avoid using io_uring for drives backed by LVM and configured for write-back\n    or write-through cache\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 07 Jul 2021 14:56:34 +0200\n\nqemu-server (7.0-9) bullseye; urgency=medium\n\n  * cpu weight: always clamp value to lower maximum for cgroup v2 and fix\n    defaults (v1 -> 1024, v2 -> 100)\n\n  * api: improve error handling when applying pending config changes\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 07 Jul 2021 12:02:13 +0200\n\nqemu-server (7.0-7) bullseye; urgency=medium\n\n  * improve #3329: ensure write-back is used over write-around for EFI disk,\n    as OVMF profits a lot from cached writes due to its frequent\n    read-modify-write operations\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 05 Jul 2021 20:49:50 +0200\n\nqemu-server (7.0-6) bullseye; urgency=medium\n\n  * live-restore: preload efidisk before starting VM\n\n  * For now do not use io_uring for drives backed by Ceph RBD, with KRBD and\n    write-back or write-through cache enabled, as in that case some polling/IO\n    may hang in QEMU 6.0.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 02 Jul 2021 09:45:06 +0200\n\nqemu-server (7.0-5) bullseye; urgency=medium\n\n  * don't default to O_DIRECT (cache=none) on btrfs without nocow\n\n  * fix #2175: api: update VM: check old drive-config for permissions too to\n    ensure a valid transition when limited to CDROM changes.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 24 Jun 2021 18:58:19 +0200\n\nqemu-server (7.0-4) bullseye; urgency=medium\n\n  * enable io-uring support by default when running QEMU 6.0 or newer\n\n  * VM start: always check if storages of volumes support correct content-type\n\n  * use KillMode 'process' for systemd scope to cope with depreacation of\n    KillMode=none\n\n  * cli, api: handle new warnings task status\n\n  * improve backup of templates with EFI disks and with SATA and IDE\n    disk controllers in use\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Jun 2021 12:57:27 +0200\n\nqemu-server (7.0-3) bullseye; urgency=medium\n\n  * vzdump: add master key support\n\n  * vzdump: drop legacy fallback logging for dirty-bitmap\n\n  * vm destroy: do not remove unreferenced disks by default\n\n  * fix #3329: turn on cache=writeback for efidisks on rbd\n\n  * avoid setting LUN number for drives when the `pvscsi` controller is used,\n    as that cannot handle multiple LUNs, increase the `scsi-id` instead\n\n  * config: limit description/comment length to 8 KiB\n\n  * migrate: enforce that image content type is available and configured on\n    target storage\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Jun 2021 11:17:52 +0200\n\nqemu-server (7.0-2) bullseye; urgency=medium\n\n  * api: clone: sort vm disks to keep numbers consistent\n\n  * api: VM status: make template property optional in return object\n\n  * add compatibility for QEMU 6.0\n\n  * destroy VM: always remove (referenced) VM state volumes\n\n  * destroy VM: also check if unused volumes are base images\n\n  * live-restore: log more similar to regular restore, outputting the user the\n    PBS repo/snapshot and target for each drive.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 28 May 2021 12:46:36 +0200\n\nqemu-server (7.0-1) pve; urgency=medium\n\n  * re-build for Proxmox VE 7 / Debian Bullseye\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 13 May 2021 19:11:18 +0200\n\nqemu-server (6.4-2) pve; urgency=medium\n\n  * fix #2862: allow sata/ide template backups\n\n  * migration: improve speed-limits for >1G connections again\n\n  * fix getting bootdisk size for new bootorder config scheme\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Apr 2021 16:16:04 +0200\n\nqemu-server (6.4-1) pve; urgency=medium\n\n  * fix the +pveN versioned machine types when PXE is used\n\n  * migration: avoid re-scanning all volumes\n\n  * migration: do not always set default speed limit if none is configured\n\n  * migration: rework logging to more humand friendly format, avoiding to much\n    output noise\n\n  * qmrestore: add live-restore option for CLI tool\n\n  * live-restore: hold 'create' lock during operation\n\n  * live-restore: don't remove VM on error, to allow an VM user to save any new\n    data before retrying the operation.\n\n  * fix #3369: auto-start vm after failed stopmode backup\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Apr 2021 16:26:54 +0200\n\nqemu-server (6.3-11) pve; urgency=medium\n\n  * enable live-restore tech preview for Proxmox Backup Server hosted backup\n    snapshots.\n\n  * drive mirror: rework periodic status reporting to be human friendlier\n\n  * drive mirror: stop logging progress for a disk once it got ready\n\n  * image convert: use human-readable units in progress report\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 15 Apr 2021 18:32:06 +0200\n\nqemu-server (6.3-10) pve; urgency=medium\n\n  * increase timeout for block (disk) resize QMP command\n\n  * fix #3314: cloudinit: IPv6 requires type 'static6'\n\n  * fix #2670: cloudinit: enable SLAAC again now that client support is there\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 30 Mar 2021 18:40:58 +0200\n\nqemu-server (6.3-9) pve; urgency=medium\n\n  * restore vma: fix applying storage-specific bandwidth limit\n\n  * snapshot: set migration caps before savevm-start\n\n  * vzdump: improve error logging for query-proxmox-support to avoid\n    false-positives\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 26 Mar 2021 09:47:27 +0100\n\nqemu-server (6.3-8) pve; urgency=medium\n\n  * qm status: sort hash keys on verbose output\n\n  * improve windows VM version pinning on VM creation\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 12 Mar 2021 10:01:09 +0100\n\nqemu-server (6.3-7) pve; urgency=medium\n\n  * vzdump: increase Proxmox Backup Server backup QMP command timeout\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 09 Mar 2021 08:21:43 +0100\n\nqemu-server (6.3-6) pve; urgency=medium\n\n  * fix #3324: clone disk: use larger blocksize for EFI disk\n\n  * fix #3301: status: add currently running machine and QEMU version to full\n    status\n\n  * api: add endpoint to list all available QEMU machine type and version\n    tuples\n\n  * always pin virtual machines with Windows as ostype to a fixed QEMU machine\n    version by default. For existing VMs with Windows based OS-type use the 5.1\n    machine version (or the next available one, for older QEMU versions) to\n    improve stability of the hardware layout from Windows point of view. Linux\n    and other OS types are not as sensitive to those changes, so keep the\n    default to the currently latest available machine versions for non-Windows\n    VMs.\n\n  * update VM: check for CDROM not just drive permissions when removing a\n    device\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 05 Mar 2021 21:42:59 +0100\n\nqemu-server (6.3-5) pve; urgency=medium\n\n  * cloudinit: add opennebula config format\n\n  * cloudinit: remove pending delete on online regenerate image\n\n  * snapshot/save-vm: periodically print progress and show information about\n    drives during snapshot\n\n  * qmeventd: explicitly close() pidfds\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 11 Feb 2021 18:05:18 +0100\n\nqemu-server (6.3-4) pve; urgency=medium\n\n  * audio: add the \"none\" dummy audio backend\n\n  * fix drive-mirror completion with cloudinit\n\n  * vm destroy: allow opt-out of purging unreferenced disks\n\n  * fix #2788: do not resume vms after backup if they were paused before\n\n  * anchor CPU flag regex to avoid arbitrary flag suffixes\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 28 Jan 2021 17:21:07 +0100\n\nqemu-server (6.3-3) pve; urgency=medium\n\n  * api: adapt VM destroy and purge description\n\n  * clone disk: fix regression in offline clone of efidisk\n\n  * cloudinit: fix cloning/restoring of cloudinit disks in raw format\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 15 Dec 2020 16:33:01 +0100\n\nqemu-server (6.3-2) pve; urgency=medium\n\n  * PBS: use improved method to assemble repository url, fixing issues when\n    using IPv6 or non-default ports\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 03 Dec 2020 18:06:25 +0100\n\nqemu-server (6.3-1) pve; urgency=medium\n\n  * deactivate volumes after storage migrate\n\n  * print query-proxmox-support result in 'full' status\n\n  * clone disk: avoid errors after disk was moved by QEMU\n\n  * replace cgroups_write by cgroup change_cpu_shares && change_cpu_quota\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 25 Nov 2020 14:30:50 +0100\n\nqemu-server (6.2-20) pve; urgency=medium\n\n  * don't migrate replicated VM whose replication job is marked for\n    removal\n\n  * ensure qmeventd service is stopped after pve-guests and pve-ha-lrm service\n    on shutdown\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 12 Nov 2020 17:08:45 +0100\n\nqemu-server (6.2-19) pve; urgency=medium\n\n  * fix #3113: unbreak drive hotplug\n\n  * qmeventd: add handling for -no-shutdown QEMU instances, to avoid errors if\n    the guest OS shuts down the VM during a backup job.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 05 Nov 2020 13:37:00 +0100\n\nqemu-server (6.2-18) pve; urgency=medium\n\n  * migrate: tell QEMU to enable dirty-bitmap migration, if supported\n\n  * partially fix #3056: always try to cancel backups when failed to start job\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Oct 2020 18:23:13 +0100\n\nqemu-server (6.2-17) pve; urgency=medium\n\n  * bootorder: don't print empty 'order=' property\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Oct 2020 16:08:57 +0200\n\nqemu-server (6.2-16) pve; urgency=medium\n\n  * fix #3010: add 'bootorder' parameter for better control of boot devices\n\n  * fix VM clone from snapshot with cloudinit disk\n\n  * fix various possible issues by avoiding conditionally declared variables\n    altogether\n\n  * PCI passthrough: fix setting VGA to 'none' when marking passed-through\n    device as 'Primary GPU'\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 19 Oct 2020 15:51:48 +0200\n\nqemu-server (6.2-15) pve; urgency=medium\n\n  * fix #2570: add 'keephugepages' config property\n\n  * vzdump: log 'finishing' state for Proxmox Backup Server jobs, to avoid\n    suggesting that the backup is stuck at 100%. This can happen when the\n    validation and mark of pre-existing chunks needs a bit longer.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Sep 2020 17:44:28 +0200\n\nqemu-server (6.2-14) pve; urgency=medium\n\n  * vzdump: allow bandwidth limit also PBS backup\n\n  * avoid a warning when checking the VMs bios\n\n  * fix #2862: properly backup (all) VM templates\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 24 Aug 2020 19:33:54 +0200\n\nqemu-server (6.2-13) pve; urgency=medium\n\n  * fix use of bandwidth limits with offline storage migrate\n\n  * allow one to add CPU features with a dot, like \"+sse4.2\", correctly\n\n  * vzdump: improve logging output and report dirty bitmap state for each disk\n\n  * vzdump: display actually uploaded chunks as 'write' speed to conform more\n    closely with the actual network transmission line-speed.\n\n  * fix #2749: vga: disable the display EDID information for the combination of\n    Windows SeaBIOS and VGA guests to avoid a reduced list of possible screen\n    resolutions. (Windows may cache the list of possible resolutions,\n    uninstalling 'Microsoft Basic Display Adapter' and rebooting may then help)\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 20 Aug 2020 11:42:47 +0200\n\nqemu-server (6.2-11) pve; urgency=medium\n\n  * fix #2857: restore: pass keyfile to pbs-restore\n\n  * fix #2728: die/warn if target is not a replication target when\n    live-migrating\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 24 Jul 2020 08:13:29 +0200\n\nqemu-server (6.2-10) pve; urgency=medium\n\n  * pass-through: fix mdev cmdline generation\n\n  * docs: add man page cpu-models.conf(5)\n\n  * start: set resume parameter for VM start anytime there is a 'vmstate' in\n    the config, not just when it has the 'suspend' lock\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 13 Jul 2020 13:37:37 +0200\n\nqemu-server (6.2-9) pve; urgency=medium\n\n  * support encrypted pbs backups\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 10 Jul 2020 14:23:46 +0200\n\nqemu-server (6.2-8) pve; urgency=medium\n\n  * backup: detect PBS features and use only supported\n\n  * fix #2671: include CPU format in man page again\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 09 Jul 2020 15:14:36 +0200\n\nqemu-server (6.2-6) pve; urgency=medium\n\n  * vzdump: fix variable redeclaration warning\n\n  * make backup log more friendlier to read for humans\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 07 Jul 2020 19:00:09 +0200\n\nqemu-server (6.2-4) pve; urgency=medium\n\n  * fix #2787: properly parse vga for vncproxy\n\n  * vncproxy: allow to request a generated VNC password\n\n  * fix #2794: allow legacy IGD passthrough\n\n  * avoid backup command timeout with PBS\n\n  * fix #2741: add VM.Config.Cloudinit permission\n\n  * enable dirty-bitmap incremental backups for PBS\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 30 Jun 2020 11:33:35 +0200\n\nqemu-server (6.2-3) pve; urgency=medium\n\n  * fix #2748: make order of cloudinit interfaces consistent\n\n  * fix #2774: add early check for non-managed volumes\n\n  * allow to force MTU for a VM net-device\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 04 Jun 2020 11:17:09 +0200\n\nqemu-server (6.2-2) pve; urgency=medium\n\n  * adapt net-device hotplug to more strict QMP schema of QEMU 5.0\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 08 May 2020 13:00:18 +0200\n\nqemu-server (6.2-1) pve; urgency=medium\n\n  * qmrestore: fix VMA restore from STDIN\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 07 May 2020 21:51:01 +0200\n\nqemu-server (6.1-20) pve; urgency=medium\n\n  * cfg2cmd: fix uninitialized value warning on OVMF w/o efidisk\n\n  * vzdump: fix backup of templates with stdout as output\n\n  * cfg2cmd: set audiodev parameter only on qemu >= 4.2\n\n  * api: allow listing custom and default CPU models\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 06 May 2020 17:16:56 +0200\n\nqemu-server (6.1-19) pve; urgency=medium\n\n  * clone: use new config_lock_shared\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 5 May 2020 11:22:04 +0200\n\nqemu-server (6.1-18) pve; urgency=medium\n\n  * vzdump: assemble: fix skipping all pending and snapshot config entries\n\n  * api/destroy: repeat early checks after locking\n\n  * migrate: skip rescan for efidisk and shared volumes\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 04 May 2020 17:36:40 +0200\n\nqemu-server (6.1-17) pve; urgency=medium\n\n  * backup: never try to freeze in stop mode backup\n\n  * Fix #2124: Add support for zstd\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 4 May 2020 14:11:50 +0200\n\nqemu-server (6.1-16) pve; urgency=medium\n\n  * spice audio: improve compatibility with QEMU versions newer than 4.2\n\n  * migrate: workaround issues with format switch on storage live migration\n\n  * fix live migration with replicated unused volumes\n\n  * importovf: improve compatibility with OVF files without default namespaces\n\n  * backup restore: use correct storage for format check for cloudinit drives\n\n  * handle stopping the storage migration NBD server better\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 29 Apr 2020 16:23:24 +0200\n\nqemu-server (6.1-14) pve; urgency=medium\n\n  * Use foreach_volume instead of foreach_drive\n\n  * Use new storage_migrate interface\n\n  * migrate: update config with changed volume IDs\n\n  * migrate: allow specifying targetstorage for offline migration\n\n  * migrate: sync_disks: use allow_rename to avoid collisions on the target\n    storage\n\n  * migrate: sync_disks: log output of storage_migrate\n\n  * migrate: also cleanup disks migrated by storage_migrate in case of failure\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 9 Apr 2020 08:56:44 +0200\n\nqemu-server (6.1-13) pve; urgency=medium\n\n  * rescan: fix call to foreach_volume\n\n  * migration: fix downtime limit auto-increase\n\n  * migrate: allow arbitrary source->target storage maps\n\n  * migrate: always check storage permissions and content type\n\n  * Include full KVM/QEMU \"-cpu\" parameter with live-migration and\n    snapshots/suspend to allow supporting custom CPU models\n\n  * fix #2318: allow phys-bits CPU setting\n\n  * allow custom CPU models\n\n  * config: harmonize bridge pattern to match the same limits of containers\n\n  * cpu config: add upcoming EPYC-Rome CPU type\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 08 Apr 2020 17:08:13 +0200\n\nqemu-server (6.1-12) pve; urgency=medium\n\n  * CPUConfig: fix module load when pmxcfs is unavailable\n\n  * migrate: fix replication false-positives\n\n  * migrate: cleanup disk/bitmaps if 'qm start' failed\n\n  * migration with targetstorage: check if target storage supports images\n\n  * fix efidisks on storages with minimum sizes bigger than OVMF_VARS.fd\n\n  * Implement volume-related helpers and use new foreach_volume\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 30 Mar 2020 10:00:13 +0200\n\nqemu-server (6.1-11) pve; urgency=medium\n\n  * vzdump: fix regression in backups for specific storage\n\n  * custom CPU models: add initial parser and verifier\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Mar 2020 09:00:24 +0100\n\nqemu-server (6.1-10) pve; urgency=medium\n\n  * version_guard: early out when major/minor version is high enough\n\n  * drive-mirror: add support for incremental sync\n\n  * migrate: add replication info to disk overview\n\n  * migrate: add live-migration of replicated disks\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 25 Mar 2020 15:16:21 +0100\n\nqemu-server (6.1-8) pve; urgency=medium\n\n  * cloudinit: make genisoimage quieter, only output errors\n\n  * Append newline to all QGA commands for compatibility with non standard\n    conforming Apple based guest agent implementation\n\n  * add experimental support for proxmox backup server\n\n  * fix #2580: api/delete: drop VM from HA resources if purge is set\n\n  * improve drive mirror completion over NBD during migration\n\n  * add secured unix socket support for NBD storage migration\n\n  * Disable memory hotplug for custom NUMA topologies and die on misaligned\n    memory for hotplug\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 20 Mar 2020 11:11:31 +0100\n\nqemu-server (6.1-7) pve; urgency=medium\n\n  * vzdump: always exclude efidisks from backups of machines currently not set\n    to use OVMF (UEFI)\n\n  * Simplify QEMU version check and require at least 3.0+\n\n  * Align size to 1 KiB bytes before doing 'qmp block_resize'\n\n  * fix #2611: use correct operation when calculating the migration bandwidth\n    limit\n\n  * fix #2612: allow input-data in guest exec and make command optional\n\n  * cpu models: add icelake-server and icelake-client\n\n  * already add models from future QEMU 4.2 release\n\n  * fix #2264: allow one to add a virtio-rng device for improved entropy\n    bandwidth in a VM\n\n  * update_disksize: also update disk size if there was no old size at all\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 09 Mar 2020 19:12:16 +0100\n\nqemu-server (6.1-6) pve; urgency=medium\n\n  * allow reading snapshot config for users with VM.Audit on a guest\n\n  * fix #2566: increase scsi limit to 31\n\n  * fix #2578: check if $target is provided in clone\n\n  * update QMP commands to reflect (future) depreacations and changes in QEMU\n\n  * resize volume: always request new size from storage after resizing\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 10 Feb 2020 06:40:43 +0100\n\nqemu-server (6.1-5) pve; urgency=medium\n\n  * Add QEMU CPU flag querying helpers\n\n  * hotplug_pending: remove redundant write/load config calls\n\n  * api: vm clone: unlink zombie target VM and firewall config at end of error\n    cleanup\n\n  * add timeout parameter to vm start API/CLI endpoint\n\n  * fix #2070: vm_start: for a migrating VM, use current format of disk if\n    possible\n\n  * hotplug_pending: make 'ssd' option non-hotpluggable, it cannot be changed\n    live on a plugged disk.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 30 Jan 2020 10:27:33 +0100\n\nqemu-server (6.1-4) pve; urgency=medium\n\n  * check if QEMU version is recent enough for requested machine type\n\n  * suspend to disk: check and enforce more strict permissions\n\n  * update disk size before local disk migration\n\n  * hide very long commandline on vm_start/migrate failure\n\n  * fix #2493: show QEMU errors in migration log\n\n  * api/restore: do not trigger autostart-after-restored task from locked\n    context\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 16 Dec 2019 16:03:25 +0100\n\nqemu-server (6.1-3) pve; urgency=medium\n\n  * create efidisk: poll the real size after volume creation, as some storages\n    need to create bigger volumes as requested, to cope with their internal\n    alignment requirements.\n\n  * fix #2469: fix qemu-img convert src_format detection, wrongly reverted.\n\n  * fix #2510: hostpci: always check if specified device exists\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 09 Dec 2019 11:44:14 +0100\n\nqemu-server (6.1-2) pve; urgency=medium\n\n  * api: allow one to remove (hibernation) vmstate\n\n  * vzdump: log QGA FS freeze/thaw tries in task log\n\n  * skip efidisk0 in hotplug\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 30 Nov 2019 18:38:36 +0100\n\nqemu-server (6.1-1) pve; urgency=medium\n\n  * fix #2367: do not allow snapshot with name PENDING\n\n  * fix #2469: fix qemu-img convert src_format detection\n\n  * implement PVE Version addition for QEMU machine allowing to introduce new\n    features while keeping migration compatibility more easily\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 26 Nov 2019 13:06:21 +0100\n\nqemu-server (6.0-17) pve; urgency=medium\n\n  * PCI(e) pass-through: ensure we fallback to the previous default \"0000\"\n    domain again.\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 23 Nov 2019 09:52:09 +0100\n\nqemu-server (6.0-16) pve; urgency=medium\n\n  * fix #2473: use of uninitialized value\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 22 Nov 2019 14:18:58 +0100\n\nqemu-server (6.0-15) pve; urgency=medium\n\n  * api/migration: fix autocomplete for targetstorage\n\n  * add 'type' to guest agent format, allowing one to choose between VirtIO\n    (default) and ISA\n\n  * clone: pre-allocate cloud-init disk for destination\n\n  * SPICE/QXL: tell Linux VMs that they can add up to 4 display when running\n    with qemu 4.1 or newer\n\n  * add support to tell showcmd helper to assume a specific forced machine\n    version when assembling a command\n\n  * refactor QemuServer to avoid cyclic module dependencies\n\n  * fix #2436: pci: do not hardcode pci domain to 0000\n\n  * add 'tags' config option for adding meta information to a VM\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 20 Nov 2019 19:41:01 +0100\n\nqemu-server (6.0-14) pve; urgency=medium\n\n  * use PVE::DataCenterConfig, use PVE::SSHInfo, use PVE::RRD for RRD data\n\n  * refactor migration IP retrieval\n\n  * add missing packages to (build-)dependencies\n\n  * fix #2457: ga: set-user-password: increase maxLength of password\n\n  * fix restoring old VM backups made with Promxox VE earlier than 2.3\n\n  * improve test mocking\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 18 Nov 2019 12:12:03 +0100\n\nqemu-server (6.0-13) pve; urgency=medium\n\n  * fix #2434: extend machine regex to support stable release machine updates\n\n  * prepare to fix #2408, #2355, #2380: use scsi-hd backend for iSCSI as well\n\n  * fix deleting pending changes for not yet existing options\n\n  * improve hugepage memory size detection\n\n  * avoid a race for VMID reservation when importing an OVF manifest to a new\n    VM\n\n  * cleanup importidsk CLI command, and say to which exact disk we imported\n\n  * add simple runtime heuristic for IOThread backup support, to ensure the VM\n    to backup was started with a recent QEMU version.\n\n  * QMPClient: ensure QMP connection is also closed in certain edge cases\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 30 Oct 2019 17:43:41 +0100\n\nqemu-server (6.0-12) pve; urgency=medium\n\n  * fix regression from 6.0-10 with vmstate restore on RBD\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 22 Oct 2019 16:31:46 +0200\n\nqemu-server (6.0-11) pve; urgency=medium\n\n  * fix #1071: VMs with IOThread enabled disks can now be backed up\n\n  * fix regression from 6.0-10 with snapshot restore and statefile\n\n  * fix regression with from 6.0-10 where forced was always assumed to be true\n    for applying pending changes\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 22 Oct 2019 12:50:18 +0200\n\nqemu-server (6.0-10) pve; urgency=medium\n\n  * fix #2344: ignore cloudinit in replication check\n\n  * fix #1291: add option purge for vm_destroy api call\n\n  * increase code re-use with pve-container for pending changes in\n    configuration\n\n  * fix #2412: only do the final configuration destroy after all the VMs\n    resources, and references in other configurations like Firewall or resource\n    pools was successfully cleaned up\n\n  * fix #2171: ensure that non filesystem based statefiles get activated on VM\n    start\n\n  * fix #2395: improve QEMU image converter to cope better with pure file based\n    sources and iSCSI source and destinations\n\n  * fix #2402: allow 1GB hugepages if 2MB is unavailable\n\n  * qemu 4.0 : add Cascadelake-Server and KnightsMill Intel CPU models\n\n  * fix #2217: don't copy cloudinit disk on clone\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Oct 2019 22:04:50 +0200\n\nqemu-server (6.0-9) pve; urgency=medium\n\n  * fix issue where a SPICE remote viewer was disconnected during live migration\n\n  * Add VM reboot API/CLI integration, allowing to reboot a VM and applying any\n    pending changes in-between\n\n  * CPU flags: allow one to add aes flag\n\n  * fix #2263: die on live migration with local cloudinit disk\n\n  * fix #2041, #2272: Add Spice enhancements\n\n  * Add support for more (up to 16) PCI(e) devices\n\n  * usb: Allow one to make SPICE USB ports USB3 capabile\n\n  * allow one to use USB3 for SPICE USB ports with VMs started already with\n    QEMU version 4.0.0, as live-migrations were not possible with this previous\n    unsupported setup anyway. Live-snapshots from VMs with a SPICE USB device\n    which was manually set (wasn't possible over Webinterface) to USB3 with a\n    machine version of 4.0.0, need to remove the \"usb3\" flag again from the\n    snapshot config when restoring it.\n\n  * rework kvm_user_version cache mechanism\n\n  * api: deletion: check also pending values for serial/usb\n\n  * migration api: explicitly clear \"online\" flag if VM is stopped to avoid\n    issues with storage migrations which are handled different for stopped VMs\n\n  * abort resize disk if current size could not be determined\n\n  * fix #2382: delete cloudinit disk before restoring\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Sep 2019 12:01:58 +0200\n\nqemu-server (6.0-7) pve; urgency=medium\n\n  * ensure new SPICE audio device works also with 'q35' based VMs\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 24 Jul 2019 15:13:35 +0200\n\nqemu-server (6.0-6) pve; urgency=medium\n\n  * Add SPICE audio device support\n\n  * fix #2275: die on invalid sendkey\n\n  * Make sometimes problematic 'hv-tlbflush' and 'hv-evmcs' CPU flags optional\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 23 Jul 2019 18:20:10 +0200\n\nqemu-server (6.0-5) pve; urgency=medium\n\n  * do not pass Proxmox VE internal startdate 'now' to QEMU, it does not\n    understands it\n\n  * use new pcie port hardware for 4.0 and newer q35 machine types\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 11 Jul 2019 19:44:28 +0200\n\nqemu-server (6.0-4) pve; urgency=medium\n\n  * fix guest shutdown if agent is configured but no timeout was passed\n\n  * cloudinit: set iso-level in genisoimage call\n\n  * migrate pre-condition check: add size to volume attributes and handle\n    storage not selected manually in storage config\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 28 Jun 2019 20:35:09 +0200\n\nqemu-server (6.0-3) pve; urgency=medium\n\n  * do not add evmcs HyperV enlightment at all for now due to incompatibillity\n    with AMD based hosts\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 25 Jun 2019 14:33:01 +0200\n\nqemu-server (6.0-2) pve; urgency=medium\n\n  * add migration precondition api endpoint\n\n  * wait for possible left over VM scopes to be gone through dbus based helper\n    on VM start\n\n  * fix #2244: Allow timeout for guest-agent shutdown\n\n  * fix #2083: Add hv_tlbflush, hv_ipi, hv_evmcs enlightenments\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 24 Jun 2019 17:46:51 +0200\n\nqemu-server (6.0-1) pve; urgency=medium\n\n  * SMBIOS: followup: allow now 512 characters for full format string\n\n  * fix #2190: Base64 encode SMBIOS value strings in order to allow more\n    characters\n\n  * allow one to add md-clear cpu flag\n\n  * add qm command for cloudinit config dump\n\n  * drop vnc x509 param, deprecated in 2.5 removed in 4.0\n\n  * Fix #1999: cli: listsnapshot: handle multiple roots and mark orphaned as\n    root\n\n  * drop references to un-maintained sheepdog plugin\n\n  * vm_resume: correctly honor $nocheck\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 14 Jun 2019 20:59:07 +0200\n\nqemu-server (6.0-0+1) pve; urgency=medium\n\n  * rebuild for Debian Buster / PVE 6.0\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 May 2019 19:12:34 +0200\n\nqemu-server (5.0-51) unstable; urgency=medium\n\n  * fix #1811: allow non root user to edit serialX: socket entries\n\n  * allow non root users to add spice usb port\n\n  * fix #1829: do not ignore format parameter when creating cloudinit\n    disk volume\n\n  * fix: #1075: Restore VM template to VM and try to convert to template\n\n  * fix #2173: use qemu-img to check cloudinit disk existence\n\n  * cloudinit: use detected format in volume name parsing\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 30 Apr 2019 14:07:59 +0000\n\nqemu-server (5.0-50) unstable; urgency=medium\n\n  * fix #2100: ignore cloudinit drive on offline migration\n\n  * honor bandwidth limits (bwlimit) for migrate, drive-mirror, clone\n    and add to API calls\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 04 Apr 2019 16:22:10 +0200\n\nqemu-server (5.0-49) unstable; urgency=medium\n\n  * return config lock in vm status\n\n  * move 'pve-snapshot-name' to common\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 21 Mar 2019 12:55:03 +0100\n\nqemu-server (5.0-48) unstable; urgency=medium\n\n  * cloud-init: allow custom network/user data files via snippets\n\n  * fix #2120: use hosts initiator name with qemu-img\n\n  * fix #2131: get correct device when deleting iothreads\n\n  * config: NIC macaddr: enforce unicast MAC addresses\n\n  * implement suspend to disk for running VMs over API and CLI\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 19 Mar 2019 13:25:38 +0100\n\nqemu-server (5.0-47) unstable; urgency=medium\n\n  * fix #2043: always stop existing systemd scopes on VM start\n\n  * use nr_hugepages from /proc/cmdline\n\n  * fix #2101: cloudinit: IPv6 ending in : not parsed as a string\n\n  * fix #1891: Add zsh command completion for qm and qmrestore\n\n  * fix #2097: allow one to set and pass the WWN parameter for IDE, SATA and\n    SCSI disks\n\n  * allow one to add IVSHMEM device to a VM configuration\n\n  * fix #2114: always set correct link status on VM network adapter hotplug\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 04 Mar 2019 10:11:00 +0100\n\nqemu-server (5.0-46) unstable; urgency=medium\n\n  * allow explicit hv-vendor-id\n\n  * allow explicit set vga with gpu passthrough\n\n  * fix #1924: add snapshot parameter\n\n  * allow to add pre- start/stop hook scripts\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 01 Feb 2019 13:04:19 +0100\n\nqemu-server (5.0-45) unstable; urgency=medium\n\n  * fix #2003: give qm terminal a terminal over ssh\n\n  * migrate: fix local disk migration with online VMs\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Jan 2019 10:42:03 +0100\n\nqemu-server (5.0-44) unstable; urgency=medium\n\n  * fix #1013 : migrate : sync_disk : --targetstorage with offline disk\n\n  * add Windows 7 PCIe quirk for adding 'hostpci' devices\n\n  * fix #2032: check that type is set before assembling a VGA device\n\n  * add configuration to command regression tests\n\n  * allow 'none' as VGA option\n\n  * fix #1267: move args to the end of qemu commandline\n\n  * clone_disk : cloudinit drive: don't clone snapshot name (snapname)\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 20 Dec 2018 10:17:47 +0100\n\nqemu-server (5.0-43) unstable; urgency=medium\n\n  * usb: fix check if machine type is q35\n\n  * qm cleanup: exit silently if VM config does not belong to node\n    anymore\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Nov 2018 12:57:49 +0100\n\nqemu-server (5.0-42) unstable; urgency=medium\n\n  * add mediated devices support\n\n  * use improved lspci\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Nov 2018 11:22:04 +0100\n\nqemu-server (5.0-41) unstable; urgency=medium\n\n  * fix #1959: add fallback for auto previously set by SLAAC\n\n  * create_vm: don't add vmgenid for ARM machines by default\n\n  * use qmeventd to execute qm cleanup\n\n  * QemuServer: remove PCI sysfs helpers\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 19 Nov 2018 14:38:42 +0100\n\nqemu-server (5.0-40) unstable; urgency=medium\n\n  * correctly check display vga type\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 12 Nov 2018 17:27:53 +0100\n\nqemu-server (5.0-39) unstable; urgency=medium\n\n  * Add `ssd` property to IDE, SATA, and SCSI drives\n\n  * fix #1952: make vga memory configurable\n\n  * fix #1969: increase max unused disks\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 09 Nov 2018 16:23:26 +0100\n\nqemu-server (5.0-38) unstable; urgency=medium\n\n  * add second qmp socket when running qemu >= 2.12 for pending event/reboot\n    changes\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 18 Oct 2018 12:28:49 +0200\n\nqemu-server (5.0-37) unstable; urgency=medium\n\n  * add virtio to the list of valid gpu types\n\n  * lower qemu requirement for hv_synic and hv_stimer to 2.12\n\n  * prepare for deprecated functionality in qemu 3.0\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 16 Oct 2018 14:53:04 +0200\n\nqemu-server (5.0-36) unstable; urgency=medium\n\n  * fix #1908: add VM Generation ID (vmgenid) device by default on new VMs and\n    allow to set on existing ones\n\n  * fix version check in qemu_machine_feature_enabled\n\n  * use qemu's internal blockdev-snapshot functions to avoid some issues with\n    snapshots on qcow2 files\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 24 Sep 2018 11:14:32 +0200\n\nqemu-server (5.0-35) unstable; urgency=medium\n\n  * add hv_synic and hv_stimer HyperV Enlightment flags on QEMU 3.0 and later\n\n  * improve setting QEMU machine type on snapshot and rollback\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 17 Sep 2018 15:29:18 +0200\n\nqemu-server (5.0-34) unstable; urgency=medium\n\n  * fix #1865: CloudInit doesn't add IPv6\n\n  * allow to add ibpb, ssbd, virt-ssbd, amd-ssbd, amd-no-ssb and pdpe1gb CPU flags\n\n  * clone: improve message for ignoring pending changes\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 13 Sep 2018 11:37:24 +0200\n\nqemu-server (5.0-33) unstable; urgency=medium\n\n  * fix #1242: allow one to setup an automatic QEMU guest agent fstrim\n    command after cloning or moving a disk live\n\n  * define return properties for vmstatus API call\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Aug 2018 14:40:46 +0200\n\nqemu-server (5.0-32) unstable; urgency=medium\n\n  * api/agent: do not dereference parameter hash before passing to\n    agent_cmd method\n\n  * api/agent: import check_agent_error method\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 30 Jul 2018 10:55:42 +0200\n\nqemu-server (5.0-31) unstable; urgency=medium\n\n  * fix #1717: delete snapshot when vm running and drive not attached\n\n  * fix qemu agent commands: add missing import of 'agent_cmd' method\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 30 Jul 2018 10:24:27 +0200\n\nqemu-server (5.0-30) unstable; urgency=medium\n\n  * Fix SPICE multi-monitor mode on VMs using q35 machine type\n\n  * extend QEMU guest agent API with file read/write, setting VM user password\n    and executing arbitrary commands in VM\n\n  * add guest command group to qm CLI tool\n\n  * add 'dryrun' to qm rescan, which only list changes but does not actually\n    saves them\n\n  * add a workaround for bug #1650: add content type filter to rescan\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Jul 2018 11:39:01 +0200\n\nqemu-server (5.0-29) unstable; urgency=medium\n\n  * reserve new VM config with create lock early\n\n  * api: allow auto vm start after create finished\n\n  * use 'system_wakeup' to resume VMs suspended by their guest OS\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 15 Jun 2018 12:11:29 +0200\n\nqemu-server (5.0-28) unstable; urgency=medium\n\n  * cloud-init: improve handling of network settings for broken\n    versions (e.g., the one currently used by CentOS 7)\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 11 Jun 2018 12:48:42 +0200\n\nqemu-server (5.0-27) unstable; urgency=medium\n\n  * qm agent: check if qga service is running\n\n  * fix #1779: vzdump: ensure guest-fsfreeze-thaw is called on error\n\n  * fix #1780: change datacenter.conf to datacenter.cfg in description\n\n  * fix logic of deleting balloon config property\n\n  * activate volume for cloudinit disk\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 05 Jun 2018 15:24:14 +0200\n\nqemu-server (5.0-26) unstable; urgency=medium\n\n  * fix #1749: do not copy pending changes when cloning a vm\n\n  * make q35 and virtio-scsi-single compatible\n\n  * fix an issue where cleanup operations run as part of a VM's systemd scope\n\n  * associate cloud-init options with with the VM.Config.Network permission\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 14 May 2018 14:04:26 +0200\n\nqemu-server (5.0-25) unstable; urgency=medium\n\n  * fix #1697: only check machine type for pxe\n\n  * fix scsi hotplug issue with q35 machines\n\n  * fix live migration with local disk issue with nbd\n\n  * switch to new method of passing disk serials to qemu\n\n  * qemu-img convert: use cache=none for ZFS to not fill up the ARC\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 13 Apr 2018 15:11:58 +0200\n\nqemu-server (5.0-24) unstable; urgency=medium\n\n  * use pve-edk2-firmware for supporting OVMF\n\n  * restore: implement rate limiting\n\n  * stop passing default '-k' (keyboard) QEMU option from datacenter.cfg\n\n  * create linked clones of templates by default\n\n  * remove legacy vm_monitor_command\n\n  * start: always stop an existing $vmid.scope\n\n  * implement cloudinit support\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Mar 2018 09:24:08 +0100\n\nqemu-server (5.0-23) unstable; urgency=medium\n\n  * fix migrate cache size parameter for qemu 2.11\n\n  * typo fixes in migrate task log\n\n  * depend on pve-qemu-kvm >= 2.9.1-9\n\n  * allow virtio-scsi+iothread controller hot-unplug\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Feb 2018 13:21:08 +0100\n\nqemu-server (5.0-22) unstable; urgency=medium\n\n  * fix #1664: nbd mirror: remove socat tunnel now that it is not required\n    anymore to avoid a 30s inactivity timeout\n\n  * fix #1569: add shared flag to disks\n\n  * fix #1662: make the 'snapshot' disk parameter work again\n\n  * correct 'snapshot' flag description\n\n  * CPU types: add EPYC and EPYC-IBPB\n\n  * forward returned errors from guest-agent commands\n\n  * add new guest-agent commands and add commands as separate api calls\n\n  * add agent flag to vm status api call\n\n  * clone/restore: make the smbios UUID unique if --unique is used\n\n  * add serial:1 to vm-status when config has a serial device configured\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 21 Feb 2018 09:56:08 +0100\n\nqemu-server (5.0-21) unstable; urgency=medium\n\n  * fix a case where with the upcoming introduction of sub-commands the spice\n    ticket could be read from a file rather than received via stdin\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 22 Jan 2018 15:13:23 +0100\n\nqemu-server (5.0-20) unstable; urgency=medium\n\n  * added spec-ctrl flag to cpu 'flags' property\n\n  * added -IBRS cpu type variants\n\n  * added Skylake-Server cpu type\n\n  * added --pretty option to 'qm showcmd'\n\n  * increase start timeout when hugepages are enabled to compensate for long\n    allocation times\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 16 Jan 2018 14:26:30 +0100\n\nqemu-server (5.0-19) unstable; urgency=medium\n\n  * add 'flags' property to cpu option to allow passing PCID support to VMs\n\n  * fix efi disk format detection\n\n  * use default values from load_defaults() when none is specified in conf\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 9 Jan 2018 15:49:35 +0100\n\nqemu-server (5.0-18) unstable; urgency=medium\n\n  * fix #1570: fix template backup with pigz\n\n  * fix #1471: change keyboard default to undef\n\n  * fix efi disks in raw mode not being fully writable\n\n  * qm terminal: add --escape option to change or disable the escape key\n\n  * enable vncproxy with vncterm for serial ports\n\n  * add termproxy api call\n\n  * stop adding the same disk as unused multiple times when they are reachable\n    through multiple storage definitions\n\n  * check if the guest agent actually runs before a fsfreeze-freeze/thaw\n\n  * update ostype documentation\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 Dec 2017 14:56:59 +0100\n\nqemu-server (5.0-17) unstable; urgency=medium\n\n  * correct cpuunits range\n\n  * check if base volumes are unused before deleting a template\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Oct 2017 14:58:00 +0200\n\nqemu-server (5.0-16) unstable; urgency=medium\n\n  * add new qm command 'importovf', to create VMs from an OVF manifest\n\n  * snapshot: use explicitly configured vmstate storage\n\n  * config: add vmstatestorage option\n\n  * VM.Snapshot.Rollback privilege added\n\n  * migration : enable mtunnel for insecure migration\n\n  * ovmf: deprecate old legay ovmf image and refactor\n\n  * improve efidisk creation\n\n  * efidisk: do not hard code efivar base image size\n\n  * fix #1441: do not unplug controllers when the mirroring is finished\n\n  * do not overwrite global signal handlers\n\n  * update_vm: sort logged parameters\n\n  * remove legacy sparsecp\n\n  * remove unused obsolete vmtar\n\n  * fix #1125: check for KVM support before starting VM\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 5 Oct 2017 11:17:26 +0200\n\nqemu-server (5.0-15) unstable; urgency=medium\n\n  * reduce migration downtime\n\n  * fix freezing logic when saving vm states\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 07 Aug 2017 10:46:33 +0200\n\nqemu-server (5.0-14) unstable; urgency=medium\n\n  * fix disk throttle parameters\n\n  * improve compatibility when live-migrating from pve-4\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 17 Jul 2017 11:17:53 +0200\n\nqemu-server (5.0-13) unstable; urgency=medium\n\n  * Fix #1417: make sure the target storage allows disk images before importing\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 13 Jul 2017 06:48:43 +0200\n\nqemu-server (5.0-12) unstable; urgency=medium\n\n  * Use default values when memory is not set in vm.conf when migrating\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 03 Jul 2017 14:39:25 +0200\n\nqemu-server (5.0-11) unstable; urgency=medium\n\n  * Fix #1417: properly check for 'images' content type when allocating disks\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 30 Jun 2017 09:33:17 +0200\n\nqemu-server (5.0-10) unstable; urgency=medium\n\n  * don't use cirrus by default for sane OS'\n\n  * replication: remove guest states to ensure no old states are exists\n\n  * allow disks on shared storages on replicated VMs\n\n  * refuse to add non-replicatable disks to replicating VMs\n\n  * config: has_feature() take default for backup into account\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Jun 2017 13:26:38 +0200\n\nqemu-server (5.0-9) unstable; urgency=medium\n\n  * migrate: use 'mtunnel' from pvecm\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Jun 2017 11:01:15 +0200\n\nqemu-server (5.0-8) unstable; urgency=medium\n\n  * migrate: pass the with_snapshots parameter\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Jun 2017 12:58:33 +0200\n\nqemu-server (5.0-7) unstable; urgency=medium\n\n  * PVE/QemuMigrate.pm: use new replication job helpers from AbstractMigrate\n\n  * Change target in replication-state when replication direction is switched\n\n  * PVE/QemuMigrate.pm: use replication job, transfer replication state\n\n  * add regression tests for get_replicatable_volumes\n\n  * get_replicatable_volumes: fix CDROM and local file/device handling\n\n  * get_replicatable_volumes: add unused volumes\n\n  * get_replicatable_volumes: skip volumes if we do not 'own' them\n\n  * PVE::QemuServer::foreach_volid - pass $attr hash to callback\n\n  * get_replicatable_volumes: skip volumes on shared storage\n\n  * PVE/API2/Qemu.pm: acquire guest_migration_lock inside worker\n\n  * Add a migration lock to avoid a replication on rollback-time\n\n  * change from dpkg-deb to dpkg-buildpackage\n\n  * fix #1405: sort pci ids by functions\n\n  * Add new qm command 'importdisk' to import external disk images\n\n  * migration: implement insecure offline migration\n\n  * check also pending changes when reverting/deleting\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Jun 2017 08:50:45 +0200\n\nqemu-server (5.0-6) unstable; urgency=medium\n\n  * fix burst length parameter names and pass them to qemu\n\n  * fix #1229: more explicit spice port allocation\n\n  * migrate: acquire guest_migration_lock during migration\n\n  * do not allow destroy if there are replication jobs\n\n  * use new PVE::ReplicationConfig class\n\n  * improve error on '{full, linked} clone not available' error\n\n  * Fix #1384: add missing decrement to calculation of socket-id\n\n  * migrate: pass ssh_info to storage_migrate\n\n  * tests: fix broken snapshot create tests\n\n  * tests: fix broken snapshot delete tests\n\n  * use ReuseAddr for vncproxy\n\n  * Fix #1361: create disk: stricter parsing of storage:size\n\n  * PVE::QemuServer::create_disks - run code inside eval\n\n  * implement get_replicatable_volumes\n\n  * add description of read/writes statistics in vzdump output\n\n  *  start vncproxy worker in the background\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 31 May 2017 09:21:27 +0200\n\nqemu-server (5.0-5) unstable; urgency=medium\n\n  * added rerror option to scsi drives\n\n  * added storage replication settings\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 28 Apr 2017 14:02:08 +0200\n\nqemu-server (5.0-4) unstable; urgency=medium\n\n  * fix target storage availability check for live migration with local\n    storage\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 21 Apr 2017 12:05:58 +0200\n\nqemu-server (5.0-3) unstable; urgency=medium\n\n  * fix vnc connections showing false errors\n\n  * fix #1338: handle writer count limit in live migration with local disks\n    with qemu 2.9\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 21 Apr 2017 11:47:07 +0200\n\nqemu-server (5.0-2) unstable; urgency=medium\n\n  * fix drive mirror readiness check\n\n  * fix uninitialized warning when deleting unset VM option\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 10 Apr 2017 16:27:59 +0200\n\nqemu-server (5.0-1) unstable; urgency=medium\n\n  * bump version for stretch\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 13 Mar 2017 11:59:35 +0100\n\nqemu-server (4.0-109) unstable; urgency=medium\n\n  * drop netcat6 dependency\n\n  * fix a case where VMs refuse to start with scsi disk ids >= 7\n\n  * add skylake to cpu models\n\n  * improve error messages\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 13 Mar 2017 11:56:14 +0100\n\nqemu-server (4.0-109) unstable; urgency=medium\n\n  * schema updates\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 09 Feb 2017 11:40:53 +0100\n\nqemu-server (4.0-108) unstable; urgency=medium\n\n  * #1260: convert moved template disk to base\n\n  * change TLS cipher suite to HIGH for spiceterm\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 31 Jan 2017 13:54:42 +0100\n\nqemu-server (4.0-107) unstable; urgency=medium\n\n  * add dependency on libpve-guest-common-perl\n\n  * add dependency on libpve-common-perl\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 25 Jan 2017 09:47:09 +0100\n\nqemu-server (4.0-106) unstable; urgency=medium\n\n  * only use scsi-block with explicit opt-in\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 25 Jan 2017 09:19:20 +0100\n\nqemu-server (4.0-105) unstable; urgency=medium\n\n  * use new PVE::Storage::check_volume_access()\n\n  * Set zero $size and continue if volume_resize() returns false\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 19 Jan 2017 09:19:45 +0100\n\nqemu-server (4.0-104) unstable; urgency=medium\n\n  * add setup_environment hook to CLIHandler classes\n\n  * cleanup: drop superfluous condition in assignment\n\n  * add romfile option to hostpci\n\n  * add with-local-disks option for live storage migration\n\n  * drive-mirror: bump timeout to 5s, add 30s inactivity timeout\n\n  * live clone_vm: suspend or freezefs before block-job-cancel\n\n  * add socat and unix socket for storage migration\n\n  * add live storage migration with vm migration\n\n  * clone live vm : add support for multiple jobs\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 12 Jan 2017 14:15:01 +0100\n\nqemu-server (4.0-103) unstable; urgency=medium\n\n  * destroy_vm: allow vdisk_free to fail\n\n  * Display volume size in log when doing a volume backup\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Dec 2016 12:40:40 +0100\n\nqemu-server (4.0-102) unstable; urgency=medium\n\n  * avoid \"No balloon device has been activated\" warnings in vmstatus\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 20 Dec 2016 10:12:55 +0100\n\nqemu-server (4.0-101) unstable; urgency=medium\n\n  * allow migration of local qcow2 snapshots\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 5 Dec 2016 12:38:54 +0100\n\nqemu-server (4.0-100) unstable; urgency=medium\n\n  * allow insecure migrations from older qemu-servers\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 02 Dec 2016 18:50:54 +0100\n\nqemu-server (4.0-99) unstable; urgency=medium\n\n  * qm agent: remove 'guest-' prefix from commands\n\n  * qm agent: pass command as second required argument\n\n  * qm agent: add output formatter\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 01 Dec 2016 07:58:34 +0100\n\nqemu-server (4.0-98) unstable; urgency=medium\n\n  * Add \"qm agent\" command\n\n  * increase timeout for guest-fsfreeze-freeze\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 30 Nov 2016 12:31:36 +0100\n\nqemu-server (4.0-97) unstable; urgency=medium\n\n  * Add entry for windows 10 and 2016 support\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Nov 2016 09:09:23 +0100\n\nqemu-server (4.0-96) unstable; urgency=medium\n\n  * restrict monitor API to Sys.Modify for most commands\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Nov 2016 10:02:37 +0100\n\nqemu-server (4.0-95) unstable; urgency=medium\n\n  * vm_shutdown: request 'stopped' state for HA enabled VMs\n\n  * use ha-manager 'stopped' state instead of 'disabled'\n\n  * switch to 'ha-manager set' (instead of 'ha-manager start/stop')\n\n  * vmstate snapshot: activate|deactivate volume\n\n  * qemu_volume_snapshot_delete : fix krbd snapshot delete\n\n  * VZDump/QemuServer: set bless class correctly\n\n  * cleanup windows version handling and hyperv enlightments\n\n  * remove unnecessary format_description properties\n\n  * register new standard option 'pve-qm-image-format'\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Nov 2016 08:11:08 +0100\n\nqemu-server (4.0-94) unstable; urgency=medium\n\n  * fix docs for iops/bps_max_length throttling options\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 03 Nov 2016 12:53:44 +0100\n\nqemu-server (4.0-93) unstable; urgency=medium\n\n  * Close #1195: support iops/bps_max_length throttling options\n\n  * fix a perl warning when failing to parse a new drive string\n\n  * fix a warning: discard is not a number\n\n  * fix #1177: allow dedicated migration network\n\n  * change default value for cpuunits to 1024\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 03 Nov 2016 10:26:03 +0100\n\nqemu-server (4.0-92) unstable; urgency=medium\n\n  * Close #351: add more info to backup log\n\n  * add qm listsnapshot call\n\n  * snapshot_list: add bash completion for vmid\n\n  * Fix #1174: remove pve-qm-drive\n\n  * enable drive-mirror with iothread for qemu 2.7 v2\n\n  * add get_running_qemu_version\n\n  * cpu hotplug : add new cpu hotplug method for qemu 2.7\n\n  * cpu hotplug : add coldplugged cpu to qemu command line\n\n  * cpu hotplug : add print_cpu_device\n\n  * numaX : use cpus option multiple time if cpulist\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 21 Oct 2016 09:13:39 +0200\n\nqemu-server (4.0-91) unstable; urgency=medium\n\n  * fix #1131: activate volume before copying efidisk\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 07 Oct 2016 08:21:19 +0200\n\nqemu-server (4.0-90) unstable; urgency=medium\n\n  * fix #1111: qm showcmd wrong escape sequence\n\n  * Avoid to parse empty property string\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 05 Oct 2016 07:13:33 +0200\n\nqemu-server (4.0-89) unstable; urgency=medium\n\n  * fix manual page (use efidisk0 instead of efidisk[N])\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Sep 2016 12:21:07 +0200\n\nqemu-server (4.0-88) unstable; urgency=medium\n\n  * restore: better error handling for vdisk deletion\n\n  * forbid migration of template with local base image\n\n  * forbid restore into existing template\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 16 Sep 2016 07:49:45 +0200\n\nqemu-server (4.0-87) unstable; urgency=medium\n\n  * add seabios bootsplash and use it\n\n  * add efidisk0 to config, use efidisk0 for efivars\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 08 Sep 2016 12:34:23 +0200\n\nqemu-server (4.0-86) unstable; urgency=medium\n\n  * hostpci: bring back multifunction pass-through shortcut\n\n  * disable drive-mirror when iothread is enabled\n\n  * hugepages: map numa node IDs to host and guest correctly\n\n  * hugepages: use hostnodes value as numanode for topology\n\n  * qemu-img convert : use default cache=unsafe instead writeback\n\n  * Fix #1057: make protection a fast-plug option\n\n  * add lock check for move_disk API call\n\n  * deactivate new volumes after clone to other node\n\n  * pass datacenter.cfg's mac_prefix to random_ether_addr\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 29 Aug 2016 10:11:07 +0200\n\nqemu-server (4.0-85) unstable; urgency=medium\n\n  * Fix #1051: typo: vpcus -> vcpus - so non-root users can change vcpus again\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 11 Jul 2016 14:46:43 +0200\n\nqemu-server (4.0-84) unstable; urgency=medium\n\n  * fix #1046: add non-snapshotted disks as unused\n\n  * fix #1040: warn early about moving a snapshotted disk\n\n  * disable usb hotplug for now\n\n  * remove old move disk snapshot check\n\n  * improve errors when trying to migrate disks with snapshots\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 11 Jul 2016 11:50:01 +0200\n\nqemu-server (4.0-83) unstable; urgency=medium\n\n  * Add hugepages option\n\n  * Refactor various parts out of QemuServer\n\n  * Include USB devices in vm_devices_list\n\n  * Add usb hotplugging\n\n  * Fix syntax in pve-q35.cfg\n\n  * Fix #146: add name to backup log\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Jun 2016 11:29:01 +0200\n\nqemu-server (4.0-82) unstable; urgency=medium\n\n  * add dependency on DBus\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Jun 2016 11:48:45 +0200\n\nqemu-server (4.0-81) unstable; urgency=medium\n\n  * Add LVM and LVMThin to QemuMigration\n\n  * add check for snapshots at migration\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 16 Jun 2016 12:10:48 +0200\n\nqemu-server (4.0-80) unstable; urgency=medium\n\n  * split old style pipe open call\n\n  * fix #1020: remove unnecessary root check for unused disks\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 09 Jun 2016 18:13:03 +0200\n\nqemu-server (4.0-79) unstable; urgency=medium\n\n  * migrate: collect migration tunnel child process (avoid zombies)\n\n  * migrate: use ssh forwarded UNIX socket tunnel\n\n  * migrate: add some more log output\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 03 Jun 2016 12:16:31 +0200\n\nqemu-server (4.0-78) unstable; urgency=medium\n\n  * use enter_systemd_scope instead of systemd-run\n\n  * fix #1010: whitelist options for permissions\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 03 Jun 2016 11:42:25 +0200\n\nqemu-server (4.0-77) unstable; urgency=medium\n\n  * do not ignore hotplug parse errors\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 31 May 2016 12:15:55 +0200\n\nqemu-server (4.0-76) unstable; urgency=medium\n\n  * allow VLAN 1 tag in qemu-kvm vms\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 18 May 2016 11:25:54 +0200\n\nqemu-server (4.0-75) unstable; urgency=medium\n\n  * fix #975, use new keyAlias feature.\n\n  * add --description to systemd scope unit\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 11 May 2016 11:15:56 +0200\n\nqemu-server (4.0-74) unstable; urgency=medium\n\n  * correctly set cpu vendor\n\n  * fix #971: don't activate shared storage in offline migration\n\n  * migrate: check if storage is available\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 01 May 2016 09:25:06 +0200\n\nqemu-server (4.0-73) unstable; urgency=medium\n\n  * restore: pass format to vma extract\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 29 Apr 2016 09:03:12 +0200\n\nqemu-server (4.0-72) unstable; urgency=medium\n\n  * vm_start : force systemctl stop if orphan scope exist\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 22 Apr 2016 11:09:52 +0200\n\nqemu-server (4.0-71) unstable; urgency=medium\n\n  * Fix #643: activate volumes before resizing\n\n  * fix #947: re-enable disk/cdrom passthrough\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 21 Apr 2016 10:34:42 +0200\n\nqemu-server (4.0-70) unstable; urgency=medium\n\n  * vm_status: return more verbose HA state\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 19 Apr 2016 09:01:24 +0200\n\nqemu-server (4.0-69) unstable; urgency=medium\n\n  * Fix #848: deactivate old volume after clone before deletion\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 Apr 2016 08:24:49 +0200\n\nqemu-server (4.0-68) unstable; urgency=medium\n\n  * change shutdown behaviour on suspended vm\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 12 Apr 2016 17:19:20 +0200\n\nqemu-server (4.0-67) unstable; urgency=medium\n\n  * use pve-doc-generator to generate man pages\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 08 Apr 2016 07:37:16 +0200\n\nqemu-server (4.0-66) unstable; urgency=medium\n\n  * use generic property string parser\n\n  * drive schema: allow 'none' again\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 01 Apr 2016 09:33:16 +0200\n\nqemu-server (4.0-65) unstable; urgency=medium\n\n  * cfg: use the 'urlencoded' format for drive model and serial\n\n  * clone: use the zeroinit filter for sparseinit storages\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Mar 2016 09:09:07 +0100\n\nqemu-server (4.0-64) unstable; urgency=medium\n\n  * Use AbstractConfig for Qemu\n\n  * fix #909: pass rate to tap_plug()\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 08 Mar 2016 11:43:28 +0100\n\nqemu-server (4.0-63) unstable; urgency=medium\n\n  * only perform scsi inquiry on device nodes\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 27 Feb 2016 10:20:50 +0100\n\nqemu-server (4.0-62) unstable; urgency=medium\n\n  * fix undefined value when starting a q35 machine VM\n\n  * kvm_user_version: update code to use our framework\n\n  * Refactor has_feature\n  \n -- Proxmox Support Team <support@proxmox.com>  Fri, 26 Feb 2016 17:02:34 +0100\n\nqemu-server (4.0-61) unstable; urgency=medium\n\n  * clone_vm : only deactivate sources volume if source vm if offline\n\n  * Refactor snapshot_create to match LXC.pm\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 25 Feb 2016 08:48:59 +0100\n\nqemu-server (4.0-60) unstable; urgency=medium\n\n  * change check for write-zeros\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 24 Feb 2016 17:19:12 +0100\n\nqemu-server (4.0-59) unstable; urgency=medium\n\n  * qemu_machine_pxe : return $machine if no pxe\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 22 Feb 2016 17:37:11 +0100\n\nqemu-server (4.0-58) unstable; urgency=medium\n\n  * deactivate volumes if vm start command fails\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 20 Feb 2016 10:26:28 +0100\n\nqemu-server (4.0-57) unstable; urgency=medium\n\n  * delete ram state files when restoring a backup\n\n  * Refactor snapshot code\n\n  * disable vnc server and add -nographic is no vga card is present\n\n  * passthrough : re-enable hyperv and add hv_vendor_id for windows\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 18 Feb 2016 12:47:51 +0100\n\nqemu-server (4.0-56) unstable; urgency=medium\n\n  * If we freeze the fs with the Qemu-Guest-Agent test if QGA is running.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 15 Feb 2016 12:51:19 +0100\n\nqemu-server (4.0-55) unstable; urgency=medium\n\n  * improve usb config parsing\n\n  * Refactor update_config_nolock -> write_config\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 12 Feb 2016 12:07:56 +0100\n\nqemu-server (4.0-54) unstable; urgency=medium\n\n  * restore: deal with new backup=0 property string\n\n  * add usb3 option for usb-devices\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 10 Feb 2016 17:49:19 +0100\n\nqemu-server (4.0-53) unstable; urgency=medium\n\n  * pass $skiplock all the way through to destroy_vm\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 08 Feb 2016 11:53:03 +0100\n\nqemu-server (4.0-52) unstable; urgency=medium\n\n  * close tunnel after migration is finish.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 02 Feb 2016 18:16:47 +0100\n\nqemu-server (4.0-51) unstable; urgency=medium\n\n  * Fix #878: disk-size format\n\n  *  Fix #879: exclusion of disk for backup\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 29 Jan 2016 10:04:41 +0100\n\nqemu-server (4.0-50) unstable; urgency=medium\n\n  * add hidden option to cpu type\n\n  * fix PVE::HA use clause so HA resources get registered\n\n  * Create firewall dir on VM restore\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 26 Jan 2016 16:57:43 +0100\n\nqemu-server (4.0-49) unstable; urgency=medium\n\n  * increase version to update test environment\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 26 Jan 2016 12:50:07 +0100\n\nqemu-server (4.0-48) unstable; urgency=medium\n\n  * use safe_string_ne for trunks\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 18 Jan 2016 16:58:23 +0100\n\nqemu-server (4.0-47) unstable; urgency=medium\n\n  * add support for network trunks\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 15 Jan 2016 17:27:32 +0100\n\nqemu-server (4.0-46) unstable; urgency=medium\n\n  * Closes #787: add Haswell-noTSX and Broadwell-noTSX cpu types\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 Jan 2016 16:42:05 +0100\n\nqemu-server (4.0-45) unstable; urgency=medium\n\n  * io throttle: pass pool parameters (*_max)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 Jan 2016 06:17:52 +0100\n\nqemu-server (4.0-44) unstable; urgency=medium\n\n  * ovmf : don't pass x-vga to vfio-pci\n\n  * check for quorum when starting a VM\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 10 Jan 2016 15:11:43 +0100\n\nqemu-server (4.0-43) unstable; urgency=medium\n\n  * disable hyper-v enlightment when xvga is enabled\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 04 Jan 2016 06:26:31 +0100\n\nqemu-server (4.0-42) unstable; urgency=medium\n\n  * add detect_zeroes option\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Dec 2015 09:20:18 +0100\n\nqemu-server (4.0-41) unstable; urgency=medium\n\n  * add new option to select BIOS (--bios <seabios|ovmf>)\n\n  * fix bug #841: replace get_used_paths with is_volume_in_use\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 10 Dec 2015 11:15:23 +0100\n\nqemu-server (4.0-40) unstable; urgency=medium\n\n  * qm: Add VMID auto completion to some commands\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 09 Dec 2015 12:22:25 +0100\n\nqemu-server (4.0-39) unstable; urgency=medium\n\n  * fix bug #828: activate disks before hotplugging them\n\n  * fix bug #783: set KillMode=none, so that systemd don't kill\n    them at shutdown\n\n  * re-enable steal time\n\n  * backup/restore firewall config with vzdump\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 24 Nov 2015 16:50:11 +0100\n\nqemu-server (4.0-38) unstable; urgency=medium\n\n  * Don't treat serial devices as a local resource if they\n    point to a socket.\n\n  * qemu_img_convert: activate source volume\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 13 Nov 2015 07:02:01 +0100\n\nqemu-server (4.0-37) unstable; urgency=medium\n\n  * add pve-bridge-hotplug script\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 06 Nov 2015 16:24:44 +0100\n\nqemu-server (4.0-36) unstable; urgency=medium\n\n  * use qom-get to check if old pxe network rom file are used\n\n  * migration: improve ipv6 case\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 06 Nov 2015 07:56:14 +0100\n\nqemu-server (4.0-35) unstable; urgency=medium\n\n  * clone: use a fullclone hash instead of $drive->{full}\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 30 Oct 2015 07:06:47 +0100\n\nqemu-server (4.0-34) unstable; urgency=medium\n\n  * use old netdevice bios files for older machine types (<= 2.4)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Oct 2015 09:08:49 +0100\n\nqemu-server (4.0-31) unstable; urgency=medium\n\n  * migrate : add nocheck for resume\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 15 Oct 2015 12:41:43 +0200\n\nqemu-server (4.0-30) unstable; urgency=medium\n\n  * qmrestore: implement bash completion\n\n  * create: add better check for unused IDs\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 05 Oct 2015 13:13:07 +0200\n\nqemu-server (4.0-29) unstable; urgency=medium\n\n  * support serial numbers and models for disks\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 30 Sep 2015 10:56:30 +0200\n\nqemu-server (4.0-28) unstable; urgency=medium\n\n  *  disable kvm_steal_time, because it's currently buggy with live migration\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Sep 2015 07:14:44 +0200\n\nqemu-server (4.0-27) unstable; urgency=medium\n\n  * allow to change boot order with VM.Config.Options privileges\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 26 Sep 2015 11:07:11 +0200\n\nqemu-server (4.0-26) unstable; urgency=medium\n\n  * migration: disable compress (already default)\n\n  * migration: enable xbzrle\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 25 Sep 2015 17:59:30 +0200\n\nqemu-server (4.0-25) unstable; urgency=medium\n\n  * fix kvm version parser for CVE stable releases\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Sep 2015 11:48:09 +0200\n\nqemu-server (4.0-24) unstable; urgency=medium\n\n  * improve VM protection mode\n\n  * pci passthough : make vfio default\n\n  * fix error message: allocate to much v-CPUs\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Sep 2015 12:28:19 +0200\n\nqemu-server (4.0-23) unstable; urgency=medium\n\n  * destroy: avoid warning about undefined 'protection' value\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 10 Sep 2015 09:38:30 +0200\n\nqemu-server (4.0-22) unstable; urgency=medium\n\n  * add possibility to restore backup on rbd in krbd mode\n\n  *  add krbd support to online snapshot\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 09 Sep 2015 07:55:31 +0200\n\nqemu-server (4.0-21) unstable; urgency=medium\n\n  * qm: implement bash completion\n\n  * add VM protection mode\n\n  * fix move_disk on RBD\n\n  * add HA resources check before destroying a VM\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 08 Sep 2015 10:51:05 +0200\n\nqemu-server (4.0-20) unstable; urgency=medium\n\n  * fix start kvm with os type 'other'\n\n  * spice console: fix 'uninitialized value in concat' due to unnamed VM\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 28 Aug 2015 11:39:52 +0200\n\nqemu-server (4.0-19) unstable; urgency=medium\n\n  * clone: also copy vm firewall config file\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 25 Aug 2015 06:50:04 +0200\n\nqemu-server (4.0-18) unstable; urgency=medium\n\n  * fix bug #688: if vm is not owner of this disk remove from config\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 20 Aug 2015 12:30:52 +0200\n\nqemu-server (4.0-17) unstable; urgency=medium\n\n  * fix bug #603: vmid.fw file not deleted\n\n  * fix bug #517: improve error message\n\n  * adapt /config and /pending API calls to force-delete\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 19 Aug 2015 15:44:53 +0200\n\nqemu-server (4.0-16) unstable; urgency=medium\n\n  * remove vm access permissions after destroy\n\n  * pending-delete: remember force-deletes\n\n  * correctly handle empty description in pending section\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 14 Aug 2015 08:09:44 +0200\n\nqemu-server (4.0-15) unstable; urgency=medium\n\n  * Fixed wrong UUID in Qemu VZDump backup\n\n  * add memory_unplug support V2\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 30 Jul 2015 11:32:51 +0200\n\nqemu-server (4.0-14) unstable; urgency=medium\n\n  * fixed bug 662: wrong subroutine for parsing startup order\n\n  * cpuflags : don't enforce with tcg mode\n\n  * cpuflags : remove enforce for cpumodel=host\n\n  * cpuflags : remove -rdtscp for Opteron cpu models\n\n  * vzdump : abort backup if disk have iothread enabled\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 27 Jul 2015 13:24:31 +0200\n\nqemu-server (4.0-13) unstable; urgency=medium\n\n  * drive-mirror: now allow interrupts at the scanning bitmap phase\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 01 Jul 2015 06:40:52 +0200\n\nqemu-server (4.0-12) unstable; urgency=medium\n\n  * remove outdated host_device format\n\n  * parse_drive: do not overwrite configured format\n\n  * fix aio O_DIRECT check for cdrom drives\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 10 Jun 2015 10:32:09 +0200\n\nqemu-server (4.0-11) unstable; urgency=medium\n\n  * activate-noawait pve-api-updates\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 01 Jun 2015 12:36:15 +0200\n\nqemu-server (4.0-10) unstable; urgency=medium\n\n  * implement cgroups through systemd-run\n\n  * implement hotplug for cpuunits\n\n  * remove old openvz fairscheduler code\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 29 May 2015 08:24:33 +0200\n\nqemu-server (4.0-9) unstable; urgency=medium\n\n  * qmp drive-mirror : set big timeout\n\n  * qemu-mirror : block job complete : use ready flag\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 12 May 2015 08:19:38 +0200\n\nqemu-server (4.0-8) unstable; urgency=medium\n\n  * trigger pve-api-updates event\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 05 May 2015 14:57:55 +0200\n\nqemu-server (4.0-7) unstable; urgency=medium\n\n  * cleanup: use new standard option 'pve-startup-order'\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Apr 2015 10:04:04 +0200\n\nqemu-server (4.0-6) unstable; urgency=medium\n\n  * fix ha resource names\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 17 Apr 2015 13:11:12 +0200\n\nqemu-server (4.0-5) unstable; urgency=medium\n\n  * use new pve-ha-manager\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 27 Mar 2015 12:48:41 +0100\n\nqemu-server (4.0-4) unstable; urgency=medium\n\n  * add qemu cpu flags optimisations for kernel 3.10\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Mar 2015 08:58:56 +0100\n\nqemu-server (4.0-3) unstable; urgency=medium\n\n  * vmstatus: use vcpus if defined\n\n  * balloon: use qom-get for guest balloon statistics\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 09 Mar 2015 08:22:47 +0100\n\nqemu-server (4.0-2) unstable; urgency=medium\n\n  * drive_add: escape \\ character\n\n  * bugfix: allow manual balloning if shares = 0\n  \n -- Proxmox Support Team <support@proxmox.com>  Fri, 06 Mar 2015 10:21:52 +0100\n\nqemu-server (4.0-1) unstable; urgency=medium\n\n  * updated for debian jessie\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 27 Feb 2015 13:00:56 +0100\n\nqemu-server (3.3-20) unstable; urgency=low\n\n  * correctly set an remove lock (fix \"qm unlock\")\n\n -- Proxmox Support Team <support@proxmox.com>  Sun, 15 Feb 2015 09:05:40 +0100\n\nqemu-server (3.3-19) unstable; urgency=low\n\n  * commit pending values when changing CDROM\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 14 Feb 2015 09:24:03 +0100\n\nqemu-server (3.3-18) unstable; urgency=low\n\n  * bugfix : add missing queues nic option in print_net\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 13 Feb 2015 09:05:21 +0100\n\nqemu-server (3.3-17) unstable; urgency=low\n\n  * fix CDROM hotplug\n  \n  * This fix hotplug for devices behind bridges\n  \n  * check possibility to rollback a snapshot early\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 12 Feb 2015 08:19:02 +0100\n\nqemu-server (3.3-16) unstable; urgency=low\n\n  * QemuServer: fix wrong binding of pci root ports, bridges or switches\n    to vfio\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 11 Feb 2015 06:32:14 +0100\n\nqemu-server (3.3-15) unstable; urgency=low\n\n  * code cleanup: add foreach_dimm sub\n  \n  * hotplug: memory hotplug option is not hotpluggable\n  \n  * fix bug 597: hotplug fix\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 09 Feb 2015 07:05:05 +0100\n\nqemu-server (3.3-14) unstable; urgency=low\n\n  * include memory hotplug patch\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Jan 2015 07:11:10 +0100\n\nqemu-server (3.3-13) unstable; urgency=low\n\n  * hotplug config: allow to enable specific features\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 27 Jan 2015 12:39:21 +0100\n\nqemu-server (3.3-12) unstable; urgency=low\n\n  * add new vcpus option\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Jan 2015 08:04:44 +0100\n\nqemu-server (3.3-11) unstable; urgency=low\n\n  * enable hotplug by default\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 21 Jan 2015 08:53:10 +0100\n\nqemu-server (3.3-10) unstable; urgency=low\n\n  * Support additional e1000 variants for VM machines\n  \n  * Add link_down flag to network config\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 20 Jan 2015 07:15:48 +0100\n\nqemu-server (3.3-9) unstable; urgency=low\n\n  * pending api : fix parsing 0 value\n\n  * fix test for ballon hotplug\n\n  * set boot strict=on to prevent booting from not listed boot devices\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 15 Jan 2015 06:23:10 +0100\n\nqemu-server (3.3-8) unstable; urgency=low\n\n  * new 'pending changes' API\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 08 Jan 2015 13:32:46 +0100\n\nqemu-server (3.3-7) unstable; urgency=low\n\n  * vzdump: use qga freeze in vzdump in snapshot mode\n  \n  * add custom numa topology support\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 22 Dec 2014 17:24:24 +0100\n\nqemu-server (3.3-6) unstable; urgency=low\n\n  * API change: snapshot_create - remove unused freezefs flag.\n  \n  * snapshot_create: call fsfreeze if agent flag is set\n\n  * qmpclient: use guest-sync-delimited\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 04 Dec 2014 12:34:50 +0100\n\nqemu-server (3.3-5) unstable; urgency=low\n\n  * drive-mirror: cleanups, avoid division by zero bug\n\n  * shutdown by Qemu Guest Agent if the agent flag is set\n\n  * savevm-end : wait that savevm is finished\n   \n -- Proxmox Support Team <support@proxmox.com>  Mon, 01 Dec 2014 09:31:17 +0100\n\nqemu-server (3.3-4) unstable; urgency=low\n\n  * serial: allow to pass arbitrary device names\n  \n  * Add check if host has enough real CPUs for starting VM\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 24 Nov 2014 07:42:10 +0100\n\nqemu-server (3.3-3) unstable; urgency=low\n\n  * drive-mirror: further improvements\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 10 Nov 2014 06:32:04 +0100\n\nqemu-server (3.3-2) unstable; urgency=low\n\n  * drive-mirror: wait that busy eq false before block-job-complete\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 07 Nov 2014 15:35:55 +0100\n\nqemu-server (3.3-1) unstable; urgency=low\n\n  * enable write zeroes optimisations\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 13 Oct 2014 10:35:49 +0200\n\nqemu-server (3.1-35) unstable; urgency=low\n\n  * fix bug #542: return VMID as integer\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 17 Sep 2014 15:52:34 +0200\n\nqemu-server (3.1-34) unstable; urgency=low\n\n  * hotplug: allow scsi and virtio-scsi disk hotplug|unplug\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 01 Sep 2014 11:35:49 +0200\n\nqemu-server (3.1-33) unstable; urgency=low\n\n  * clone_vm: auto generate new uuid\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 26 Aug 2014 09:23:58 +0200\n\nqemu-server (3.1-32) unstable; urgency=low\n\n  *  add Broadwell cpu model\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 20 Aug 2014 12:21:08 +0200\n\nqemu-server (3.1-31) unstable; urgency=low\n\n  * fix bug 529: fix virtio-serial when using q35 machines\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 Aug 2014 06:17:08 +0200\n\nqemu-server (3.1-30) unstable; urgency=low\n\n  * bump max hostpci to 4\n  \n  * set vga=none if x-vga passthrough is enabled\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 06 Aug 2014 09:41:36 +0200\n\nqemu-server (3.1-29) unstable; urgency=low\n\n  * vm_stop: do not use ha commands if $migratedfrom is set\n  \n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Jul 2014 06:53:05 +0200\n\nqemu-server (3.1-28) unstable; urgency=low\n\n  * disable kvm cpu signature if x-vga is enabled\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 24 Jul 2014 06:52:25 +0200\n\nqemu-server (3.1-27) unstable; urgency=low\n\n  * pci passthrough: Reset device only if has_fl_reset is defined\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Jul 2014 06:11:49 +0200\n\nqemu-server (3.1-26) unstable; urgency=low\n\n  * enable linked clones from snapshots (ceph)\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 17 Jul 2014 09:28:47 +0200\n\nqemu-server (3.1-25) unstable; urgency=low\n\n  * allow resize of virtio windows boot disk (virtio-win-0.1-74 have \n    fixed the resize bug)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 16 Jul 2014 12:48:03 +0200\n\nqemu-server (3.1-24) unstable; urgency=low\n\n  * new option smbios1: specify SMBIOS type 1 fields (uuid, ...)\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Jun 2014 11:13:27 +0200\n\nqemu-server (3.1-23) unstable; urgency=low\n\n  * vncproxy: remove check if VM is running\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 25 Jun 2014 09:56:02 +0200\n\nqemu-server (3.1-22) unstable; urgency=low\n\n  * hostpci: add pci multifunction support\n  \n  * hostpci: add pcie and x-vga passthrough\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 25 Jun 2014 09:31:30 +0200\n\nqemu-server (3.1-21) unstable; urgency=low\n\n  * protect websocket API with vncticket\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 24 Jun 2014 17:43:25 +0200\n\nqemu-server (3.1-20) unstable; urgency=low\n\n  * vncwebsocket: do not proxy connection (not required)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 18 Jun 2014 12:45:39 +0200\n\nqemu-server (3.1-19) unstable; urgency=low\n\n  * do not depend on novnc-pve package (use new HTTPServer feature instead)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 18 Jun 2014 11:04:48 +0200\n\nqemu-server (3.1-18) unstable; urgency=low\n\n  * depend on novnc-pve package\n  \n  * support webockets for VNC console\n  \n  * migration : add setup state\n\n  * add virtio-net multiqueue support\n  \n  * add firewall option to qemu network interface\n  \n  * add initiator-name to iscsi drives if configured\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Jun 2014 09:00:03 +0200\n\nqemu-server (3.1-17) unstable; urgency=low\n\n  * depend on pve-firewall package\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 06 May 2014 11:28:26 +0200\n\nqemu-server (3.1-16) unstable; urgency=low\n\n  * fix bug #502: allow creation of empty vma archives\n  \n  * fix bug #510: move_disk - don't delete disk if used in a previous snasphot\n\n  * qmrestore: removed short timeout\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 17 Apr 2014 09:29:26 +0200\n\nqemu-server (3.1-15) unstable; urgency=low\n\n  * correctly disable unwanted migrate features (xbzrle).\n  \n  * QemuMigrate: print migration xbzrle if enabled\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 10 Feb 2014 08:06:29 +0100\n\nqemu-server (3.1-14) unstable; urgency=low\n  \n  * Add title of VM to Spice Client\n\n  * deactivate volume after move_disk\n  \n  * add cpu_hotplug (and maxcpus config)\n  \n  * migration : enable auto-converge capability\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 29 Jan 2014 06:49:16 +0100\n\nqemu-server (3.1-13) unstable; urgency=low\n\n  * pci passthrough: new option to disable device ROM (rombar=off)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 13 Dec 2013 11:45:46 +0100\n\nqemu-server (3.1-12) unstable; urgency=low\n\n  * spiceproxy: use POST instead of GET\n  \n -- Proxmox Support Team <support@proxmox.com>  Tue, 10 Dec 2013 10:49:30 +0100\n\nqemu-server (3.1-11) unstable; urgency=low\n\n  * depend on pve-qemu-kvm >= 1.7-1\n  \n  * fix 'qm unlink' command syntax\n  \n  * add 'pvscsi' to the list of scsi controllers (emulate the VMware\n    PVSCSI device)\n\n  * add 'lsi53c810' to the list of scsi controllers (supported on some \n    very old Windows NT versions)\n\n  * add 'vmxnet3' to the list of available network card models (emulate\n    VMware paravirtualized network card)\n  \n  * add drive option 'discard'\n  \n  * add support for new qemu throttling burst max parameters.\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 03 Dec 2013 10:48:33 +0100\n\nqemu-server (3.1-10) unstable; urgency=low\n\n  * add +lahf_lm flag to kvm64 cpudef\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 29 Nov 2013 09:18:39 +0100\n\nqemu-server (3.1-9) unstable; urgency=low\n  \n  * clone: deactivate volumes after clone to other node\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 19 Nov 2013 08:17:35 +0100\n\nqemu-server (3.1-8) unstable; urgency=low\n\n  * clone: correctly check drive options (avoid feature is not available bug)\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 14 Oct 2013 07:36:26 +0200\n\nqemu-server (3.1-7) unstable; urgency=low\n\n  * spice: add multi-monitors support\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 02 Oct 2013 09:13:23 +0200\n\nqemu-server (3.1-6) unstable; urgency=low\n\n  * use new PVE::Storage::abs_filesystem_path()\n  \n  * use warnings instead of global -w flag\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 01 Oct 2013 12:42:42 +0200\n\nqemu-server (3.1-5) unstable; urgency=low\n\n  * introduce new ostype 'solaris' (run without x2apic).\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 24 Sep 2013 06:55:33 +0200\n\nqemu-server (3.1-4) unstable; urgency=low\n\n  * qemu migrate : only wait for spice server online + eval\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 19 Sep 2013 06:29:39 +0200\n\nqemu-server (3.1-3) unstable; urgency=low\n\n  * speedup restore on glusterfs (do not write zero bytes)\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Sep 2013 09:13:34 +0200\n\nqemu-server (3.1-2) unstable; urgency=low\n\n  * Allow VMAdmin/DatastoreUser to delete disk \n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 05 Sep 2013 07:48:07 +0200\n\nqemu-server (3.1-1) unstable; urgency=low\n\n  * allow pass through of usb parallel devices (--parallel0 /dev/usb/lp0)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 14 Aug 2013 12:20:23 +0200\n\nqemu-server (3.0-30) unstable; urgency=low\n\n  * fix bugs in migration code (wrong qw() usage)\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 12 Aug 2013 09:49:20 +0200\n\nqemu-server (3.0-29) unstable; urgency=low\n\n  * vncproxy: load config from correct node\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 06 Aug 2013 08:15:59 +0200\n\nqemu-server (3.0-28) unstable; urgency=low\n\n  * allow to use a socket for serial devices\n  \n  * implement 'qm terminal' to open terminal via serial device\n  \n  * add ability to run without graphic card ('vga: serial[n]')\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 02 Aug 2013 10:05:35 +0200\n\nqemu-server (3.0-27) unstable; urgency=low\n\n  * add support for insecure/fast migration (setting in datacenter.cfg)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 26 Jul 2013 11:24:36 +0200\n\nqemu-server (3.0-26) unstable; urgency=low\n\n  * remove spice cert paths (depend on pve-qemu-kvm >= 1.4-16)\n  \n  * implement spice seamless migration\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 23 Jul 2013 10:08:33 +0200\n\nqemu-server (3.0-25) unstable; urgency=low\n\n  * support usb redirection devices for spice (usb[n]: spice)\n  \n  * disable tablet device by default for spice\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 19 Jul 2013 09:38:25 +0200\n\nqemu-server (3.0-24) unstable; urgency=low\n  \n  * spiceproxy API: allow client to choose proxy address\n  \n  * spiceproxy API: read cert subject name directly using Net::SSLeay\n  \n  * spice: use TLS (encrypt whole traffic)\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 18 Jul 2013 08:14:46 +0200\n\nqemu-server (3.0-23) unstable; urgency=low\n\n  * allow to pass SCSI generic devices to guests, for example \"scsi0: /dev/sg5\"\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 16 Jul 2013 06:49:45 +0200\n\nqemu-server (3.0-22) unstable; urgency=low\n\n  * cpu flags optimization\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 15 Jul 2013 09:14:49 +0200\n\nqemu-server (3.0-21) unstable; urgency=low\n\n  * add support for SPICE\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 26 Jun 2013 13:15:48 +0200\n\nqemu-server (3.0-20) unstable; urgency=low\n\n  * new API to update VM config: this one is fully asynchronous.\n  \n  * snapshot rollback: use pc-i440fx-1.4 as default\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 07 Jun 2013 11:44:16 +0200\n\nqemu-server (3.0-19) unstable; urgency=low\n\n  * config: implement new 'machine' configuration\n  \n  * migrate:  pass --machine parameter to remote 'qm start' command\n\n  * snapshot: store/use 'machine' confifiguration\n  \n -- Proxmox Support Team <support@proxmox.com>  Wed, 05 Jun 2013 10:27:47 +0200\n\nqemu-server (3.0-18) unstable; urgency=low\n\n  * implement delete flag for move_disk\n  \n  * API: rename move to move_disk\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 31 May 2013 10:57:50 +0200\n\nqemu-server (3.0-17) unstable; urgency=low\n\n  * implement storage migration (\"qm move\")\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 29 May 2013 13:01:16 +0200\n\nqemu-server (3.0-16) unstable; urgency=low\n\n  * fix bug 395: correctly handle unused disk with storage alias\n\n  * fix unused disk handling (do not hide unused disks when used with snapshot).\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 28 May 2013 12:23:12 +0200\n\nqemu-server (3.0-15) unstable; urgency=low\n\n  * qemu_img_format : use raw for as default for other storage\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 23 May 2013 11:33:47 +0200\n\nqemu-server (3.0-14) unstable; urgency=low\n\n  * fix bug #389: avoid error if balloon is undefined\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 May 2013 07:17:38 +0200\n\nqemu-server (3.0-13) unstable; urgency=low\n\n  * fix bug #391 - restore: test if requested format is supported\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 21 May 2013 12:03:56 +0200\n\nqemu-server (3.0-12) unstable; urgency=low\n\n  * use add_vm_to_pool/remove_vm_from_pool from PVE::AccessControl\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 14 May 2013 12:02:41 +0200\n\nqemu-server (3.0-11) unstable; urgency=low\n\n  * clone disk : keep source volume params\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 14 May 2013 10:18:21 +0200\n\nqemu-server (3.0-10) unstable; urgency=low\n\n  * fix bug #381: use PVE::Tools::next_migrate_port()\n  \n  * clone: check is we can clone to target storage\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 13 May 2013 07:31:47 +0200\n\nqemu-server (3.0-9) unstable; urgency=low\n\n  * restore: do not restore template flag\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 08 May 2013 10:23:49 +0200\n\nqemu-server (3.0-8) unstable; urgency=low\n\n  * qemu_img_format: use 'raw' for lvm\n\n  * drive-mirror : die if stats are empty\n\n  * set long timeout for query-block-jobs\n  \n -- Proxmox Support Team <support@proxmox.com>  Tue, 07 May 2013 10:19:19 +0200\n\nqemu-server (3.0-7) unstable; urgency=low\n\n  * has_feature: return list of allowed nodes\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 06 May 2013 08:58:35 +0200\n\nqemu-server (3.0-6) unstable; urgency=low\n\n  * fix bug #379 - restore: allow to overwrite existing VMs if user has \n    VM.Backup permissions\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 03 May 2013 07:53:40 +0200\n\nqemu-server (3.0-5) unstable; urgency=low\n\n  * implement shared file locks\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 25 Apr 2013 11:35:01 +0200\n\nqemu-server (3.0-4) unstable; urgency=low\n\n  * fix bug 377: make qm rescan work properly\n  \n  * avoid endless loop in QMPClient\n  \n  * use vm create permissions for templates\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 24 Apr 2013 07:59:28 +0200\n\nqemu-server (3.0-3) unstable; urgency=low\n\n  * allow sparse restore for sheepdog and rbd\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 18 Apr 2013 06:21:40 +0200\n\nqemu-server (3.0-2) unstable; urgency=low\n\n  * fix warnings in syslog\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 22 Mar 2013 06:25:12 +0100\n\nqemu-server (3.0-1) unstable; urgency=low\n\n  * start 3.0 development\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 05 Mar 2013 11:39:08 +0100\n\nqemu-server (2.3-17) unstable; urgency=low\n\n  * fix backup-cancel timeout\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 01 Mar 2013 10:58:58 +0100\n\nqemu-server (2.3-16) unstable; urgency=low\n\n  * vncproxy (another try): wait max 10s for the socket if it does not\n    exist\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 01 Mar 2013 06:42:36 +0100\n\nqemu-server (2.3-15) unstable; urgency=low\n\n  * revert vncproxy change because it breaks console on remote hosts.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 28 Feb 2013 12:53:59 +0100\n\nqemu-server (2.3-14) unstable; urgency=low\n\n  * bugfix #340 : don't set cache=none to cdrom\n  \n  * Migrate: fix check if a backing file exist  \n  \n  * vncproxy: wait max 10s for the socket if it does not exist\n  \n  * vzdump: improve error reporting\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 28 Feb 2013 06:21:30 +0100\n\nqemu-server (2.3-13) unstable; urgency=low\n\n  * set cache=none by default\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 25 Feb 2013 06:12:14 +0100\n\nqemu-server (2.3-12) unstable; urgency=low\n\n  * disable hotplug by default (revert previous change)\n  \n  * disable usb2 controller by default (revert previous change)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 22 Feb 2013 09:52:13 +0100\n\nqemu-server (2.3-11) unstable; urgency=low\n\n  * fix backup parameters for pve-qemu-kvm 1.4-4\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 20 Feb 2013 10:47:55 +0100\n\nqemu-server (2.3-10) unstable; urgency=low\n\n  * enable hotplug by default\n  \n  * enable usb2 controller by default\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 19 Feb 2013 10:45:28 +0100\n\nqemu-server (2.3-9) unstable; urgency=low\n\n  * depend on pve-qemu-kvm >= 1.4-1\n\n  * qemu 1.4 fix : rename stats-polling-interval to\n    guest-stats-polling-interval\n  \n  * remove expected_downtime from migration status (not set in qemu 1.4,\n    always 0)\n\n  * do not set cache=none for .raw files - use qemu default instead\n    (writeback)\n  \n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 Feb 2013 10:38:13 +0100\n\nqemu-server (2.3-8) unstable; urgency=low\n\n  * fix tar restore: correctly check if VM config already exists\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 28 Jan 2013 09:53:12 +0100\n\nqemu-server (2.3-7) unstable; urgency=low\n\n  * allow to suspend/resume VM during backup\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 17 Jan 2013 10:25:17 +0100\n\nqemu-server (2.3-6) unstable; urgency=low\n\n  * fix bug #315: cancel backup before stopping the VM\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 16 Jan 2013 13:22:58 +0100\n\nqemu-server (2.3-5) unstable; urgency=low\n\n  * fix bug #307: correctly restore disk settings\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 07 Jan 2013 06:48:48 +0100\n\nqemu-server (2.3-4) unstable; urgency=low\n\n  * qmrestore vma files: when restoring to same VMID, only remove volumes\n    contained in backup - other volumes will be still available as\n    'unused' disks. Note: tar restore still removes all volumes.\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 04 Jan 2013 07:02:00 +0100\n\nqemu-server (2.3-3) unstable; urgency=low\n\n  * fix Bug #293: CDROM size not reset when set to use no media\n  \n  * set migrate_downtime default value to 0.1 (and use float instead of int)\n\n  * implement dynamic migration_downtime\n  \n  * allow manual ballooning if shares is set to zero\n  \n  * new api2 call: vm_feature (test support for snapshots/clone feature)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 02 Jan 2013 06:31:38 +0100\n\nqemu-server (2.3-2) unstable; urgency=low\n\n  * use memory info from ballon driver (if enabled)\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 18 Dec 2012 12:51:57 +0100\n\nqemu-server (2.3-1) unstable; urgency=low\n\n  * include new qemu backup feature\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 12 Dec 2012 15:14:59 +0100\n\nqemu-server (2.0-71) unstable; urgency=low\n\n  * show better error message if bridge does not exist\n  \n  * QMPClient: support fdsets\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 11 Dec 2012 11:17:47 +0100\n\nqemu-server (2.0-70) unstable; urgency=low\n\n  * fix version parser for qemu 1.3\n\n  * add new cpu types: SandyBridge Haswell Opteron_G4 Opteron_G5\n\n  * remove rhel specific cpu types: cpu64-rhel6 cpu64-rhel5\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Nov 2012 07:45:19 +0100\n\nqemu-server (2.0-69) unstable; urgency=low\n\n  * fix ballon monitor command (use bytes instead of MBs)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 16 Nov 2012 06:13:46 +0100\n\nqemu-server (2.0-68) unstable; urgency=low\n\n  * vzdump: store drive in correct order (sort) to avoid confusion\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 05 Nov 2012 06:25:33 +0100\n\nqemu-server (2.0-67) unstable; urgency=low\n\n  * fix allocation size in qmrestore\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 03 Nov 2012 07:54:06 +0100\n\nqemu-server (2.0-66) unstable; urgency=low\n\n  * vzdump: restore sata drives correctly\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 02 Nov 2012 07:43:16 +0100\n\nqemu-server (2.0-65) unstable; urgency=low\n\n  * remove hardcoded blowfish cipher\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 31 Oct 2012 13:58:27 +0100\n\nqemu-server (2.0-64) unstable; urgency=low\n\n  * fix memory leak in QMP Client (many thanks to Stefan!)\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 29 Oct 2012 12:14:51 +0100\n\nqemu-server (2.0-63) unstable; urgency=low\n\n  * fix bug in vmtar \n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 25 Oct 2012 10:00:50 +0200\n\nqemu-server (2.0-62) unstable; urgency=low\n\n  * vncproxy: wait until vnc port is ready\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 24 Oct 2012 08:58:52 +0200\n\nqemu-server (2.0-61) unstable; urgency=low\n\n  * add 'win8' ostype (same defaults as win7 for now)\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 23 Oct 2012 09:35:27 +0200\n\nqemu-server (2.0-60) unstable; urgency=low\n\n  * fix bug 258:  use bridge mtu for tap interfaces\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 22 Oct 2012 12:26:42 +0200\n\nqemu-server (2.0-59) unstable; urgency=low\n\n  * disable vzdump for VM containing snapshots\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 27 Sep 2012 09:41:19 +0200\n\nqemu-server (2.0-58) unstable; urgency=low\n\n  * fix bug 251: use new command line syntax\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 26 Sep 2012 12:43:17 +0200\n\nqemu-server (2.0-57) unstable; urgency=low\n\n  * fix migrating VMs with snapshots\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 25 Sep 2012 08:12:37 +0200\n\nqemu-server (2.0-56) unstable; urgency=low\n\n  * include shanpshot support\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 24 Sep 2012 15:59:40 +0200\n\nqemu-server (2.0-55) unstable; urgency=low\n\n  * migrate: disable xbzrle (not stable)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 31 Aug 2012 11:04:35 +0200\n\nqemu-server (2.0-54) unstable; urgency=low\n\n  * use mbps instead of bps\n  \n  * try to make migrate more stable\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 30 Aug 2012 12:16:39 +0200\n\nqemu-server (2.0-53) unstable; urgency=low\n\n  * livemigrate : activate xbzrle cache\n\n  * scsihw: add megasas controller\n  \n  * partly fix bug 247: retry qmp open \n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 27 Aug 2012 13:45:18 +0200\n\nqemu-server (2.0-52) unstable; urgency=low\n\n  * fix bug 217: stop VM after failed migrate\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 23 Aug 2012 10:33:57 +0200\n\nqemu-server (2.0-51) unstable; urgency=low\n\n  * support up to 32 network devices\n  \n  * support up to 16 virtio devices\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 21 Aug 2012 10:06:39 +0200\n\nqemu-server (2.0-50) unstable; urgency=low\n\n  * implement 'qm rescan' to update disk sizes and unused disk info\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Aug 2012 09:22:29 +0200\n\nqemu-server (2.0-49) unstable; urgency=low\n\n  * fix bug 242: re-add old monitor code\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 17 Aug 2012 10:33:37 +0200\n\nqemu-server (2.0-48) unstable; urgency=low\n\n  * add size hint to drive options\n  \n  * new 'qm resize' command\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 14 Aug 2012 06:55:39 +0200\n\nqemu-server (2.0-47) unstable; urgency=low\n\n  * implement virtio-scsi-pci controller\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 01 Aug 2012 08:57:52 +0200\n\nqemu-server (2.0-46) unstable; urgency=low\n\n  * bug fix: allow to set devices directly (-ide1 /dev/XYZ)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 27 Jul 2012 11:59:14 +0200\n\nqemu-server (2.0-45) unstable; urgency=low\n\n  * migrate: only scan available storages\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 16 Jul 2012 10:18:22 +0200\n\nqemu-server (2.0-44) unstable; urgency=low\n\n  * migrate: only scan necessary storage for unused volumes\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 16 Jul 2012 06:59:23 +0200\n\nqemu-server (2.0-43) unstable; urgency=low\n\n  * use new QMPClient code\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 13 Jul 2012 07:05:28 +0200\n\nqemu-server (2.0-42) unstable; urgency=low\n\n  * fix pool permission checks on create\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 30 May 2012 10:13:11 +0200\n\nqemu-server (2.0-41) unstable; urgency=low\n\n  * more fixes for newer pve-storage versions\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 24 May 2012 07:24:00 +0200\n\nqemu-server (2.0-40) unstable; urgency=low\n\n  * minor fixes for newer pve-storage versions\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 May 2012 07:23:31 +0200\n\nqemu-server (2.0-39) unstable; urgency=low\n\n  * new startup option to define startup order.\n  \n  * do not start VMs with /etc/init.d/qemu-server. This is now\n  done with /etc/init.d/pve-manager\n  \n  * qm: removed startall/stopall commands\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 19 Apr 2012 14:26:04 +0200\n\nqemu-server (2.0-38) unstable; urgency=low\n\n  * add directsync cache mode\n  \n  * fix bug #147: allow to set migrate_downtime to 0\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 11 Apr 2012 08:51:56 +0200\n\nqemu-server (2.0-37) unstable; urgency=low\n\n  * fix bug in storage availability check (migrate)\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 07 Apr 2012 08:25:59 +0200\n\nqemu-server (2.0-36) unstable; urgency=low\n\n  * use '-no-kvm-pit-reinjection -no-hpet' for win7 and w2k8 guests.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 05 Apr 2012 12:34:00 +0200\n\nqemu-server (2.0-35) unstable; urgency=low\n\n  * fix bug #134: allow to pass file names to qmrestore and 'qm set'\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 02 Apr 2012 10:51:41 +0200\n\nqemu-server (2.0-34) unstable; urgency=low\n\n  * fix bug #12: check storage availability early (migrate)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 30 Mar 2012 09:12:46 +0200\n\nqemu-server (2.0-33) unstable; urgency=low\n\n  * fix bug #121: activate volumes correctly\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Mar 2012 11:09:36 +0200\n\nqemu-server (2.0-32) unstable; urgency=low\n\n  * fix usb host syntax (correctly pass hexadecimal numbers with prefix\n    '0x' to kvm)\n  \n  * document rerror/werror options\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 29 Mar 2012 07:11:51 +0200\n\nqemu-server (2.0-31) unstable; urgency=low\n\n  * use new network setup code from PVE::Network (libpve-common-perl)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Mar 2012 10:39:12 +0200\n\nqemu-server (2.0-30) unstable; urgency=low\n\n  * fix ha migration\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 27 Mar 2012 12:20:33 +0200\n\nqemu-server (2.0-29) unstable; urgency=low\n\n  * fix bug #97: execute 'clusvcadm' commands for HA managed VMs\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 27 Mar 2012 10:36:23 +0200\n\nqemu-server (2.0-28) unstable; urgency=low\n\n  * use Digest::SHA instead of Digest::SHA1\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 20 Mar 2012 12:24:01 +0100\n\nqemu-server (2.0-27) unstable; urgency=low\n\n  * make startall wait up to 10 seconds for quorum\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 20 Mar 2012 09:32:53 +0100\n\nqemu-server (2.0-26) unstable; urgency=low\n\n  * fix bug 109: use scsi inquiry to test if we can use the scsi-block\n    driver.\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 19 Mar 2012 10:39:30 +0100\n\nqemu-server (2.0-25) unstable; urgency=low\n\n  * fix bug 102: remove stale status file on stop\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 01 Mar 2012 12:53:32 +0100\n\nqemu-server (2.0-24) unstable; urgency=low\n\n  * save description as comment.\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 01 Mar 2012 08:12:29 +0100\n\nqemu-server (2.0-23) unstable; urgency=low\n\n  * fix lvremove call: avoid 'Not a CODE reference' warning\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 01 Mar 2012 06:36:33 +0100\n\nqemu-server (2.0-22) unstable; urgency=low\n\n  * revert tablet mice fix - does not work reliable\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 29 Feb 2012 09:45:30 +0100\n\nqemu-server (2.0-21) unstable; urgency=low\n\n  * fix tablet mice as default when live migrate\n  \n -- Proxmox Support Team <support@proxmox.com>  Wed, 29 Feb 2012 06:51:09 +0100\n\nqemu-server (2.0-20) unstable; urgency=low\n\n  * fix bug 96: fix vzdump on stopped vm\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 24 Feb 2012 07:38:45 +0100\n\nqemu-server (2.0-19) unstable; urgency=low\n\n  * support more CPU models\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Feb 2012 07:18:53 +0100\n\nqemu-server (2.0-18) unstable; urgency=low\n\n  * fix cdrom (/dev/cdrom) permission check\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Feb 2012 07:16:36 +0100\n\nqemu-server (2.0-17) unstable; urgency=low\n\n  * fix cdrom removal bug\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 15 Feb 2012 10:48:30 +0100\n\nqemu-server (2.0-16) unstable; urgency=low\n\n  * ignore -tdf (avoid kvm warning) - this is no longer needed \n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 13 Feb 2012 11:18:01 +0100\n\nqemu-server (2.0-15) unstable; urgency=low\n\n  * online migration fix: close tunnel later, wait for connection close\n  \n  * fix bug #81: do no deactivate volumes in vzdump stop mode\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 17 Jan 2012 11:24:56 +0100\n\nqemu-server (2.0-14) unstable; urgency=low\n\n  * use 'da' instead of 'dk' for Danish keyboard (qemu use that name)\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 09 Jan 2012 11:24:40 +0100\n\nqemu-server (2.0-13) unstable; urgency=low\n\n  * load vhost_net module\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 20 Dec 2011 12:25:22 +0100\n\nqemu-server (2.0-12) unstable; urgency=low\n\n  * implement forceStop option for shutdown\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 15 Dec 2011 12:58:00 +0100\n\nqemu-server (2.0-11) unstable; urgency=low\n\n  * do not use ehci by default\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 08 Dec 2011 10:26:36 +0100\n\nqemu-server (2.0-10) unstable; urgency=low\n\n  * set qm exit codes correctly\n  \n  * fix 'qm shutdown <vmid>'\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 30 Nov 2011 09:35:43 +0100\n\nqemu-server (2.0-9) unstable; urgency=low\n\n  * fix 'qm stopall'\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Nov 2011 11:14:01 +0100\n\nqemu-server (2.0-8) unstable; urgency=low\n\n  * be more careful when removing snapshots\n  \n  * do not call check_lock() for sendkey\n  \n  * try to detect errors before starting the background task\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Nov 2011 08:08:44 +0100\n\nqemu-server (2.0-7) unstable; urgency=low\n\n  * activate/deactivate LVs more carefully\n  \n  * avoid syslog whenever possible\n  \n  * code cleanups\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 25 Nov 2011 08:08:04 +0100\n\nqemu-server (2.0-6) unstable; urgency=low\n\n  * set correct migrate speed\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 23 Nov 2011 09:12:12 +0100\n\nqemu-server (2.0-5) unstable; urgency=low\n\n  * fix vzdump stop mode\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 21 Nov 2011 06:38:03 +0100\n\nqemu-server (2.0-4) unstable; urgency=low\n\n  * bump version\n\n -- Proxmox Support Team <support@proxmox.com>  Sat, 19 Nov 2011 09:54:19 +0100\n\nqemu-server (2.0-3) unstable; urgency=low\n\n  * implement monitor API\n  \n  * implement qmrestore\n\n  * fix vzdump plugin for 2.0\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 09 Nov 2011 11:35:58 +0100\n\nqemu-server (2.0-2) unstable; urgency=low\n\n  * cleanups\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 05 Oct 2011 10:15:37 +0200\n\nqemu-server (2.0-1) unstable; urgency=low\n\n  * see Changelog for details\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Aug 2010 13:48:12 +0200\n\nqemu-server (1.1-18) unstable; urgency=low\n\n  * small bug fix im qmigrate\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 20 Aug 2010 08:05:21 +0200\n\nqemu-server (1.1-17) unstable; urgency=low\n\n  * carefully catch write errors\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 19 Jul 2010 09:00:48 +0200\n\nqemu-server (1.1-16) unstable; urgency=low\n\n  * add rerror/werror options (patch from l.mierzwa)\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 29 Jun 2010 08:49:00 +0200\n\nqemu-server (1.1-15) unstable; urgency=low\n\n  * fix vmtar bug (endless growing archive)\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 25 Jun 2010 12:22:17 +0200\n\nqemu-server (1.1-14) unstable; urgency=low\n\n  *  correct order of config option (prevent virtio reordering)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Apr 2010 09:05:15 +0200\n\nqemu-server (1.1-13) unstable; urgency=low\n\n  * allow vlan1-to vlan4094 (Since 802.1q allows VLAN identifiers up to 4094).\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 21 Apr 2010 10:19:37 +0200\n\nqemu-server (1.1-12) unstable; urgency=low\n\n  * minor fixes for new qemu-kvm 0.12.3\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 16 Apr 2010 12:01:17 +0200\n\nqemu-server (1.1-11) unstable; urgency=low\n\n  * experimental support for pci pass-through (option 'hostpci')\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 08 Jan 2010 13:03:44 +0100\n\nqemu-server (1.1-10) unstable; urgency=low\n\n  * add compatibility code for older kvm versions (0.9)\n  \n  * only use fairsched when the kernel has openvz support\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 04 Dec 2009 15:17:18 +0100\n\nqemu-server (1.1-9) unstable; urgency=low\n\n  * always display boot menu (Press F12...)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 28 Oct 2009 10:28:23 +0100\n\nqemu-server (1.1-8) unstable; urgency=low\n\n  * fix 'stopall' timeout\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 23 Oct 2009 12:55:23 +0200\n\nqemu-server (1.1-7) unstable; urgency=low\n\n  * do not set fairsched --id when using virtio\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 22 Oct 2009 11:57:57 +0200\n\nqemu-server (1.1-6) unstable; urgency=low\n\n  * disable fairsched when option 'cpuunits' is set to 0 (zero)\n\n  * disable fairsched when the VM uses virtio devices.\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 15 Oct 2009 15:06:48 +0200\n\nqemu-server (1.1-5) unstable; urgency=low\n\n  * suppress syslog when setting migrate downtime/speed\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 06 Oct 2009 10:10:55 +0200\n\nqemu-server (1.1-4) unstable; urgency=low\n\n  * depend on stable pve-qemu-kvm\n\n  * new migrate_speed and migrate_downtime settings\n  \n -- Proxmox Support Team <support@proxmox.com>  Mon, 28 Sep 2009 11:18:08 +0200\n\nqemu-server (1.1-3) unstable; urgency=low\n\n  * support up to 1000 vlans\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Sep 2009 09:54:35 +0200\n\nqemu-server (1.1-2) unstable; urgency=low\n\n  * introduce new 'sockets' and 'cores' settings\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 18 Sep 2009 09:54:18 +0200\n\nqemu-server (1.1-1) unstable; urgency=low\n\n  * use new pve-storage framework\n  \n  * delete unused disk on destroy\n\n  * fix cache= option (none|writethrough|writeback)\n\n  * use rtc-td-hack for windows when acpi is enabled\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 25 Jun 2009 08:49:53 +0200\n\nqemu-server (1.0-14) unstable; urgency=low\n\n  * add new tablet option (to disable --usbdevice tablet, which generate\n    many interrupts, which is bad when you run many VMs) (Patch was\n    provided by Tomasz Chmielewski)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 27 May 2009 12:50:45 +0200\n\nqemu-server (1.0-13) unstable; urgency=low\n\n  * Conflict with netcat-openbsd\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 13 May 2009 10:20:54 +0200\n\nqemu-server (1.0-12) unstable; urgency=low\n\n  * fixes for debian lenny\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 21 Apr 2009 14:28:42 +0200\n\nqemu-server (1.0-11) unstable; urgency=low\n\n  *  allow white spaces inside args - use normal shell quoting\n  \n -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Feb 2009 11:31:36 +0100\n\nqemu-server (1.0-10) unstable; urgency=low\n\n  * add 'args' option\n\n  * bug fix for 'lost description'\n  \n -- Proxmox Support Team <support@proxmox.com>  Wed, 11 Feb 2009 08:18:29 +0100\n\nqemu-server (1.0-9) unstable; urgency=low\n\n  * add 'parallel' option\n  \n  * add 'startdate' option\n  \n  * fix manual page\n\n -- Proxmox Support Team <support@proxmox.com>  Mon,  2 Feb 2009 08:53:26 +0100\n\nqemu-server (1.0-8) unstable; urgency=low\n\n  * add 'serial' option\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 20 Jan 2009 08:52:24 +0100\n\nqemu-server (1.0-7) unstable; urgency=low\n\n  * use new syntax for kvm vga option (needed for kvm newer than kvm77)\n\n -- Proxmox Support Team <support@proxmox.com>  Wed,  7 Jan 2009 14:46:09 +0100\n\nqemu-server (1.0-6) unstable; urgency=low\n\n  * use predefined names for tap devices\n\n -- Proxmox Support Team <support@proxmox.com>  Fri, 19 Dec 2008 13:00:44 +0100\n\nqemu-server (1.0-5) unstable; urgency=low\n\n  * added host usb device support\n\n -- Proxmox Support Team <support@proxmox.com>  Mon, 17 Nov 2008 11:26:04 +0100\n\nqemu-server (1.0-4) unstable; urgency=low\n\n  * fix status problem\n\n -- Proxmox Support Team <support@proxmox.com>  Thu, 13 Nov 2008 13:13:43 +0100\n\nqemu-server (1.0-3) unstable; urgency=low\n\n  * small bug fixes\n\n -- Proxmox Support Team <support@proxmox.com>  Tue, 11 Nov 2008 08:29:23 +0100\n\nqemu-server (1.0-1) unstable; urgency=low\n\n  * update for kvm-75, support vm migration\n\n -- Proxmox Support Team <support@proxmox.com>  Wed, 22 Oct 2008 11:04:03 +0200\n\nqemu-server (0.9) unstable; urgency=low\n\n  * initial release\n\n -- Proxmox Support Team <support@proxmox.com>  Mon,  4 Feb 2008 09:10:13 +0100\n\n"
  },
  {
    "path": "debian/control",
    "content": "Source: qemu-server\nSection: admin\nPriority: optional\nMaintainer: Proxmox Support Team <support@proxmox.com>\nBuild-Depends: debhelper-compat (= 13),\n               libclass-methodmaker-perl,\n               libglib2.0-dev,\n               libio-multiplex-perl,\n               libjson-c-dev,\n               libnet-dbus-perl,\n               libpve-apiclient-perl,\n               libpve-cluster-perl,\n               libpve-common-perl (>= 9.1.9),\n               libpve-guest-common-perl (>= 5.2.2),\n               libpve-network-perl,\n               libpve-storage-perl (>= 9.0.16),\n               libtest-mockmodule-perl,\n               liburi-perl,\n               libuuid-perl,\n               lintian,\n               perl,\n               pkgconf,\n               pve-cluster,\n               pve-doc-generator (>= 6.2-5),\n               pve-edk2-firmware-ovmf (>= 4.2025.05-2),\n               pve-firewall,\n               pve-ha-manager <!nocheck>,\n               pve-qemu-kvm (>= 10.1~),\nStandards-Version: 4.5.1\nHomepage: https://www.proxmox.com\n\nPackage: qemu-server\nArchitecture: any\nDepends: conntrack,\n         dbus,\n         genisoimage,\n         libclass-methodmaker-perl,\n         libio-multiplex-perl,\n         libjson-perl,\n         libjson-xs-perl,\n         libnet-dbus-perl,\n         libnet-ssleay-perl,\n         libpve-access-control (>= 9.0.7~),\n         libpve-apiclient-perl,\n         libpve-cluster-perl,\n         libpve-common-perl (>= 9.1.11~),\n         libpve-guest-common-perl (>= 5.2.2),\n         libpve-storage-perl (>= 9.0.16),\n         libterm-readline-gnu-perl,\n         liburi-perl,\n         libuuid-perl,\n         perl (>= 5.10.0-19),\n         proxmox-termproxy (>= 2.1.0),\n         proxmox-websocket-tunnel,\n         pve-cluster,\n# TODO: make legacy edk2 optional (suggests) for PVE 9 and warn explicitly about it\n         pve-edk2-firmware-legacy | pve-edk2-firmware (<< 4~),\n         pve-edk2-firmware-ovmf (>= 4.2025.05-2),\n         pve-firewall (>= 6.0.3),\n         pve-ha-manager (>= 5.0.3),\n         pve-qemu-kvm (>= 7.1~),\n         python3-virt-firmware,\n         socat,\n         swtpm,\n         swtpm-tools,\n         ${misc:Depends},\n         ${perl:Depends},\n         ${shlibs:Depends},\nRecommends: libpve-network-perl (>= 0.8.3),\n            proxmox-backup-file-restore (>= 2.1.9-2),\n            virtiofsd,\nSuggests: pve-edk2-firmware-aarch64, pve-edk2-firmware-riscv, proxmox-firewall (>= 1.1.1),\nBreaks: pve-ha-manager (<< 4.0.1), pve-manager (<= 6.0-13),\nDescription: Qemu Server Tools\n This package contains the Qemu Server tools used by Proxmox VE\n"
  },
  {
    "path": "debian/copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\nFiles: *\nCopyright: 2011 - 2026 Proxmox Server Solutions GmbH <support@proxmox.com>\nLicense: AGPL-3+\n\nLicense: AGPL-3+\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n .\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n GNU Affero General Public License for more details.\n .\n You should have received a copy of the GNU Affero General Public License\n along with this program.  If not, see <http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "debian/docs",
    "content": "debian/SOURCE\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n# -*- makefile -*-\n# Sample debian/rules that uses debhelper.\n# This file was originally written by Joey Hess and Craig Small.\n# As a special exception, when this file is copied by dh-make into a\n# dh-make output file, you may use that output file without restriction.\n# This special exception was added by Craig Small in version 0.37 of dh-make.\n\n# Uncomment this to turn on verbose mode.\n#export DH_VERBOSE=1\n\n%:\n\tdh $@\n"
  },
  {
    "path": "debian/source/format",
    "content": "3.0 (native)\n"
  },
  {
    "path": "debian/triggers",
    "content": "activate-noawait pve-api-updates\n"
  },
  {
    "path": "src/Makefile",
    "content": "all:\n\n.PHONY: install\ninstall:\n\t$(MAKE) -C PVE install\n\t$(MAKE) -C bin install\n\t$(MAKE) -C qmeventd install\n\t$(MAKE) -C query-machine-capabilities install\n\t$(MAKE) -C usr install\n\n.PHONY: test\ntest:\n\t$(MAKE) -C test\n\t$(MAKE) -C bin $@\n\n.PHONY: clean\nclean:\n\t$(MAKE) -C test $@\n\t$(MAKE) -C bin $@\n\n.PHONY: distclean\ndistclean: clean\n"
  },
  {
    "path": "src/PVE/API2/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\n.PHONY: install\ninstall:\n\tinstall -d -m 0755 $(DESTDIR)$(PERLDIR)/PVE/API2\n\tinstall -D -m 0644 Qemu.pm $(DESTDIR)$(PERLDIR)/PVE/API2/Qemu.pm\n\t$(MAKE) -C Qemu install\n\t$(MAKE) -C NodeCapabilities install\n"
  },
  {
    "path": "src/PVE/API2/NodeCapabilities/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES := Qemu/Migration.pm\n\n.PHONY: install\ninstall: $(SOURCES)\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/API2/NodeCapabilities/$$i; done\n"
  },
  {
    "path": "src/PVE/API2/NodeCapabilities/Qemu/Migration.pm",
    "content": "package PVE::API2::NodeCapabilities::Qemu::Migration;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::RESTHandler;\n\nuse base qw(PVE::RESTHandler);\n\n__PACKAGE__->register_method({\n    name => 'capabilities',\n    path => '',\n    method => 'GET',\n    proxyto => 'node',\n    description => 'Get node-specific QEMU migration capabilities of the node.'\n        . \" Requires the 'Sys.Audit' permission on '/nodes/<node>'.\",\n    permissions => {\n        check => ['perm', '/nodes/{node}', ['Sys.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n        },\n    },\n    returns => {\n        type => 'object',\n        additionalProperties => 0,\n        properties => {\n            'has-dbus-vmstate' => {\n                type => 'boolean',\n                description => 'Whether the host supports live-migrating additional'\n                    . ' VM state via the dbus-vmstate helper.',\n            },\n        },\n    },\n    code => sub {\n        return {\n            'has-dbus-vmstate' => -f '/usr/libexec/qemu-server/dbus-vmstate'\n            ? JSON::true\n            : JSON::false,\n        };\n    },\n});\n\n1;\n"
  },
  {
    "path": "src/PVE/API2/Qemu/Agent.pm",
    "content": "package PVE::API2::Qemu::Agent;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse MIME::Base64 qw(encode_base64 decode_base64);\n\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::RESTHandler;\n\nuse PVE::QemuConfig;\nuse PVE::QemuServer;\nuse PVE::QemuServer::Agent qw(agent_cmd check_agent_error);\nuse PVE::QemuServer::Monitor qw(mon_cmd);\n\nuse base qw(PVE::RESTHandler);\n\n# max size for file-read over the api\nmy $MAX_READ_SIZE = 16 * 1024 * 1024; # 16 MiB\n\n# list of commands\n# will generate one api endpoint per command\n# needs a 'method' property and a 'perms' property\nmy $guest_agent_commands = {\n    'ping' => {\n        method => 'POST',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-time' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'info' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'fsfreeze-status' => {\n        method => 'POST',\n        perms => {\n            check => [\n                'perm',\n                '/vms/{vmid}',\n                [\n                    'VM.GuestAgent.Audit',\n                    'VM.GuestAgent.FileSystemMgmt',\n                    'VM.GuestAgent.Unrestricted',\n                ],\n                any => 1,\n            ],\n        },\n    },\n    'fsfreeze-freeze' => {\n        method => 'POST',\n        perms => 'VM.GuestAgent.FileSystemMgmt',\n    },\n    'fsfreeze-thaw' => {\n        method => 'POST',\n        perms => 'VM.GuestAgent.FileSystemMgmt',\n    },\n    'fstrim' => {\n        method => 'POST',\n        perms => 'VM.GuestAgent.FileSystemMgmt',\n    },\n    'network-get-interfaces' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-vcpus' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-fsinfo' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-memory-blocks' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-memory-block-info' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'suspend-hybrid' => {\n        method => 'POST',\n        perms => 'VM.PowerMgmt',\n    },\n    'suspend-ram' => {\n        method => 'POST',\n        perms => 'VM.PowerMgmt',\n    },\n    'suspend-disk' => {\n        method => 'POST',\n        perms => 'VM.PowerMgmt',\n    },\n    'shutdown' => {\n        method => 'POST',\n        perms => 'VM.PowerMgmt',\n    },\n    # added since qemu 2.9\n    'get-host-name' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-osinfo' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-users' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n    'get-timezone' => {\n        method => 'GET',\n        perms => 'VM.GuestAgent.Audit',\n    },\n};\n\n__PACKAGE__->register_method({\n    name => 'index',\n    path => '',\n    proxyto => 'node',\n    method => 'GET',\n    description => \"QEMU Guest Agent command index.\",\n    permissions => {\n        user => 'all',\n    },\n    parameters => {\n        additionalProperties => 1,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => \"object\",\n            properties => {},\n        },\n        links => [{ rel => 'child', href => '{name}' }],\n        description => \"Returns the list of QEMU Guest Agent commands\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $result = [];\n\n        my $cmds = [keys %$guest_agent_commands];\n        push @$cmds, qw(\n            exec\n            exec-status\n            file-read\n            file-write\n            set-user-password\n        );\n\n        for my $cmd (sort @$cmds) {\n            push @$result, { name => $cmd };\n        }\n\n        return $result;\n    },\n});\n\nsub register_command {\n    my ($class, $command, $method, $perm) = @_;\n\n    die \"no method given\\n\" if !$method;\n    die \"no command given\\n\" if !defined($command);\n\n    my $permission;\n\n    if (ref($perm) eq 'HASH') {\n        $permission = $perm;\n    } else {\n        die \"internal error: missing permission for $command\" if !$perm;\n\n        $permission = {\n            check => ['perm', '/vms/{vmid}', [$perm, 'VM.GuestAgent.Unrestricted'], any => 1],\n        };\n    }\n\n    my $parameters = {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                {\n                    completion => \\&PVE::QemuServer::complete_vmid_running,\n                },\n            ),\n            command => {\n                type => 'string',\n                description => \"The QGA command.\",\n                enum => [sort keys %$guest_agent_commands],\n            },\n        },\n    };\n\n    my $description = \"Execute QEMU Guest Agent commands.\";\n    my $name = 'agent';\n\n    if ($command ne '') {\n        $description = \"Execute $command.\";\n        $name = $command;\n        delete $parameters->{properties}->{command};\n    }\n\n    __PACKAGE__->register_method({\n        name => $name,\n        path => $command,\n        method => $method,\n        protected => 1,\n        proxyto => 'node',\n        description => $description,\n        permissions => $permission,\n        parameters => $parameters,\n        returns => {\n            type => 'object',\n            description => \"Returns an object with a single `result` property.\",\n        },\n        code => sub {\n            my ($param) = @_;\n\n            my $vmid = $param->{vmid};\n\n            my $conf = PVE::QemuConfig->load_config($vmid); # check if VM exists\n\n            PVE::QemuServer::Agent::assert_agent_available($vmid, $conf);\n\n            my $cmd = $param->{command} // $command;\n            my $res = mon_cmd($vmid, \"guest-$cmd\");\n\n            return { result => $res };\n        },\n    });\n}\n\n# old {vmid}/agent POST endpoint, here for compatibility\n__PACKAGE__->register_command('', 'POST', 'VM.GuestAgent.Unrestricted');\n\nfor my $cmd (sort keys %$guest_agent_commands) {\n    my $props = $guest_agent_commands->{$cmd};\n    __PACKAGE__->register_command($cmd, $props->{method}, $props->{perms});\n}\n\n# commands with parameters are complicated and we want to register them manually\n__PACKAGE__->register_method({\n    name => 'set-user-password',\n    path => 'set-user-password',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Sets the password for the given user to the given password\",\n    permissions => { check => ['perm', '/vms/{vmid}', ['VM.GuestAgent.Unrestricted']] },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            username => {\n                type => 'string',\n                description => 'The user to set the password for.',\n            },\n            password => {\n                type => 'string',\n                description => 'The new password.',\n                minLength => 5,\n                maxLength => 1024,\n            },\n            crypted => {\n                type => 'boolean',\n                description =>\n                    'set to 1 if the password has already been passed through crypt()',\n                optional => 1,\n                default => 0,\n            },\n        },\n    },\n    returns => {\n        type => 'object',\n        description => \"Returns an object with a single `result` property.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $crypted = $param->{crypted} // 0;\n        my $args = {\n            username => $param->{username},\n            password => encode_base64($param->{password}),\n            crypted => $crypted ? JSON::true : JSON::false,\n        };\n        my $res =\n            agent_cmd($vmid, $conf, \"set-user-password\", $args, 'cannot set user password');\n\n        return { result => $res };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'exec',\n    path => 'exec',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description =>\n        \"Executes the given command in the vm via the guest-agent and returns an object with the pid.\",\n    permissions => { check => ['perm', '/vms/{vmid}', ['VM.GuestAgent.Unrestricted']] },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            command => {\n                type => 'array',\n                description => 'The command as a list of program + arguments.',\n                items => {\n                    type => 'string',\n                    description => 'A single part of the program + arguments.',\n                },\n            },\n            'input-data' => {\n                type => 'string',\n                maxLength => 64 * 1024,\n                description =>\n                    \"Data to pass as 'input-data' to the guest. Usually treated as STDIN to 'command'.\",\n                optional => 1,\n            },\n        },\n    },\n    returns => {\n        type => 'object',\n        properties => {\n            pid => {\n                type => 'integer',\n                description => \"The PID of the process started by the guest-agent.\",\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $cmd = $param->{command};\n\n        my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $conf, $param->{'input-data'}, $cmd);\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'exec-status',\n    path => 'exec-status',\n    method => 'GET',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Gets the status of the given pid started by the guest-agent\",\n    permissions => { check => ['perm', '/vms/{vmid}', ['VM.GuestAgent.Unrestricted']] },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            pid => {\n                type => 'integer',\n                description => 'The PID to query',\n            },\n        },\n    },\n    returns => {\n        type => 'object',\n        properties => {\n            exited => {\n                type => 'boolean',\n                description => 'Tells if the given command has exited yet.',\n            },\n            exitcode => {\n                type => 'integer',\n                optional => 1,\n                description => 'process exit code if it was normally terminated.',\n            },\n            signal => {\n                type => 'integer',\n                optional => 1,\n                description =>\n                    'signal number or exception code if the process was abnormally terminated.',\n            },\n            'out-data' => {\n                type => 'string',\n                optional => 1,\n                description => 'stdout of the process',\n            },\n            'err-data' => {\n                type => 'string',\n                optional => 1,\n                description => 'stderr of the process',\n            },\n            'out-truncated' => {\n                type => 'boolean',\n                optional => 1,\n                description => 'true if stdout was not fully captured',\n            },\n            'err-truncated' => {\n                type => 'boolean',\n                optional => 1,\n                description => 'true if stderr was not fully captured',\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $pid = int($param->{pid});\n\n        my $res = PVE::QemuServer::Agent::qemu_exec_status($vmid, $conf, $pid);\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'file-read',\n    path => 'file-read',\n    method => 'GET',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Reads the given file via guest agent. Is limited to $MAX_READ_SIZE bytes.\",\n    permissions => {\n        check => [\n            'perm',\n            '/vms/{vmid}',\n            ['VM.GuestAgent.FileRead', 'VM.GuestAgent.Unrestricted'],\n            any => 1,\n        ],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            count => {\n                type => 'integer',\n                optional => 1,\n                minimum => 1,\n                maximum => $MAX_READ_SIZE,\n                default => $MAX_READ_SIZE,\n                description => \"Number of bytes to read.\",\n            },\n            decode => {\n                type => 'boolean',\n                optional => 1,\n                default => 1,\n                description => \"Data received from the QEMU Guest-Agent is base64 encoded.\"\n                    . \" If this is set to true, the data is decoded.\"\n                    . \" Otherwise the content is forwarded with base64 encoding.\"\n                    . \" Defaults to true.\",\n            },\n            file => {\n                type => 'string',\n                description => 'The path to the file',\n            },\n            offset => {\n                type => 'integer',\n                optional => 1,\n                minimum => 0,\n                default => 0,\n                description => \"Offset to start reading at\",\n            },\n        },\n    },\n    returns => {\n        type => 'object',\n        description => \"Returns an object with a `content` property.\",\n        properties => {\n            content => {\n                type => 'string',\n                description => \"The content of the file, maximum $MAX_READ_SIZE\",\n            },\n            truncated => {\n                type => 'boolean',\n                optional => 1,\n                description => \"If set to 1, the read did not reach the end of the file.\",\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n        my $count = $param->{count} // $MAX_READ_SIZE;\n        my $decode = $param->{decode} // 1;\n        my $offset = $param->{offset} // 0;\n\n        my $vmid = $param->{vmid};\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $qgafh =\n            agent_cmd($vmid, $conf, \"file-open\", { path => $param->{file} }, \"can't open file\");\n\n        if ($offset > 0) {\n            my $seek = mon_cmd(\n                $vmid, \"guest-file-seek\",\n                handle => $qgafh,\n                offset => int($offset),\n                whence => 'set',\n            );\n            check_agent_error($seek, \"can't seek to offset position\");\n        }\n\n        my $bytes_read = 0;\n        my $eof = 0;\n        my $read_size = 1024 * 1024;\n        my $content = \"\";\n\n        while ($bytes_read < $count && !$eof) {\n            my $bytes_left = $count - $bytes_read;\n            my $chunk_size = $bytes_left < $read_size ? $bytes_left : $read_size;\n            my $read =\n                mon_cmd($vmid, \"guest-file-read\", handle => $qgafh, count => int($chunk_size));\n            check_agent_error($read, \"can't read from file\");\n\n            my $chunk = $read->{'buf-b64'};\n            $chunk = decode_base64($chunk) if $decode;\n            $content .= $chunk;\n\n            $bytes_read += $read->{count};\n            $eof = $read->{eof} // 0;\n        }\n\n        my $res = mon_cmd($vmid, \"guest-file-close\", handle => $qgafh);\n        check_agent_error($res, \"can't close file\", 1);\n\n        my $result = {\n            content => $content,\n            'bytes-read' => $bytes_read,\n        };\n\n        if (!$eof) {\n            if (!defined($param->{count})) {\n                warn \"agent file-read: reached maximum read size: $MAX_READ_SIZE bytes.\"\n                    . \" output might be truncated.\\n\";\n            }\n            $result->{truncated} = 1;\n        }\n\n        return $result;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'file-write',\n    path => 'file-write',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Writes the given file via guest agent.\",\n    permissions => {\n        check => [\n            'perm',\n            '/vms/{vmid}',\n            ['VM.GuestAgent.FileWrite', 'VM.GuestAgent.Unrestricted'],\n            any => 1,\n        ],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            file => {\n                type => 'string',\n                description => 'The path to the file.',\n            },\n            content => {\n                type => 'string',\n                maxLength => 60 * 1024, # 60k, smaller than our 64k POST limit\n                description => \"The content to write into the file.\",\n            },\n            encode => {\n                type => 'boolean',\n                description =>\n                    \"If set, the content will be encoded as base64 (required by QEMU).\"\n                    . \"Otherwise the content needs to be encoded beforehand - defaults to true.\",\n                optional => 1,\n                default => 1,\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $buf =\n            ($param->{encode} // 1) ? encode_base64($param->{content}) : $param->{content};\n\n        my $qgafh = agent_cmd(\n            $vmid,\n            $conf,\n            \"file-open\",\n            { path => $param->{file}, mode => 'wb' },\n            \"can't open file\",\n        );\n\n        agent_cmd(\n            $vmid,\n            $conf,\n            \"file-write\",\n            { handle => $qgafh, 'buf-b64' => $buf },\n            \"can't write to file\",\n        );\n\n        agent_cmd($vmid, $conf, \"file-close\", { handle => $qgafh }, \"can't close file\");\n\n        return;\n    },\n});\n\n1;\n"
  },
  {
    "path": "src/PVE/API2/Qemu/CPU.pm",
    "content": "package PVE::API2::Qemu::CPU;\n\nuse strict;\nuse warnings;\n\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::RPCEnvironment;\nuse PVE::RESTHandler;\nuse PVE::Tools qw(extract_param);\n\nuse PVE::QemuServer::CPUConfig;\n\nuse base qw(PVE::RESTHandler);\n\n__PACKAGE__->register_method({\n    name => 'index',\n    path => '',\n    method => 'GET',\n    description => 'List all custom and default CPU models.',\n    permissions => {\n        user => 'all',\n        description => 'Only returns custom models when the current user has'\n            . ' Sys.Audit on /nodes.',\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            arch => get_standard_option('pve-qm-cpu-arch', { optional => 1 }),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => 'object',\n            properties => {\n                name => {\n                    type => 'string',\n                    description => \"Name of the CPU model. Identifies it for\"\n                        . \" subsequent API calls. Prefixed with\"\n                        . \" 'custom-' for custom models.\",\n                },\n                custom => {\n                    type => 'boolean',\n                    description => \"True if this is a custom CPU model.\",\n                },\n                vendor => {\n                    type => 'string',\n                    description => \"CPU vendor visible to the guest when this\"\n                        . \" model is selected. Vendor of\"\n                        . \" 'reported-model' in case of custom models.\",\n                },\n            },\n        },\n        links => [{ rel => 'child', href => '{name}' }],\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n        my $include_custom = $rpcenv->check($authuser, \"/nodes\", ['Sys.Audit'], 1);\n\n        my $arch = extract_param($param, 'arch');\n\n        return PVE::QemuServer::CPUConfig::get_cpu_models($include_custom, $arch);\n    },\n});\n\n1;\n"
  },
  {
    "path": "src/PVE/API2/Qemu/CPUFlags.pm",
    "content": "package PVE::API2::Qemu::CPUFlags;\n\nuse v5.36;\n\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::RESTHandler;\nuse PVE::Tools qw(extract_param);\n\nuse PVE::QemuServer::CPUConfig;\n\nuse base qw(PVE::RESTHandler);\n\n__PACKAGE__->register_method({\n    name => 'index',\n    path => '',\n    method => 'GET',\n    description => 'List of available VM-specific CPU flags.',\n    permissions => { user => 'all' },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            arch => get_standard_option('pve-qm-cpu-arch', { optional => 1 }),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => 'object',\n            properties => {\n                name => {\n                    type => 'string',\n                    description => \"Name of the CPU flag.\",\n                },\n                description => {\n                    type => 'string',\n                    description => \"Description of the CPU flag.\",\n                },\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $arch = extract_param($param, 'arch');\n\n        return PVE::QemuServer::CPUConfig::get_supported_cpu_flags($arch);\n    },\n});\n\n1;\n"
  },
  {
    "path": "src/PVE/API2/Qemu/HMPPerms.pm",
    "content": "package PVE::API2::Qemu::HMPPerms;\n\nuse strict;\nuse warnings;\n\n# List of monitor commands and associated required permission. Listed explicitly to be future-proof.\n#\n# Currently permissions are:\n# 'root' - for root-only commands\n# 'Sys.Modify' - commands that can be issued with 'Sys.Modify' on '/'\n# 'none' - no permissions required (i.e. help and info)\nour $hmp_command_perms = {\n    help => 'none', # show the help\n    '?' => 'none', # short-form of 'help'\n    info => 'none', # show various information about the system state\n\n    # root-only: backup to arbitrary target file (although currently, not overwriting existing file)\n    backup => 'root', # create a VM backup (VMA format).\n    # root-only: requires the stream source in the backing chain currently, but better be safe\n    block_stream => 'root', # copy data from a backing file into a block device\n    # root-only: allows changing the path a removable medium points to\n    change => 'root', # change a removable medium\n    # root-only: among others, there is a 'file' driver\n    'chardev-add' => 'root', # add chardev\n    # root-only: among others, there is a 'file' driver (e.g. modify backend for serial device)\n    'chardev-change' => 'root', # change chardev\n    # root-only: because chardev-add is\n    'chardev-remove' => 'root', # remove chardev\n    # root-only: after migration SPICE client will attempt to connect to arbitrarily set host\n    client_migrate_info => 'root', # set migration information for remote display\n    # root-only: like '-device' on the commandline\n    device_add => 'root', # add device, like -device on the command line\n    # root-only: because device_add is\n    device_del => 'root', # remove device\n    # root-only: like '-drive' on the commandline\n    drive_add => 'root', # add drive to PCI storage controller\n    # root-only: backup to arbitrary target file\n    drive_backup => 'root', # initiates a point-in-time copy for a device.\n    # root-only: because drive_add is\n    drive_del => 'root', # remove host block device\n    # root-only: mirror to arbitrary target file\n    drive_mirror => 'root', # initiates live storage migration for a device.\n    # root-only: dump guest memory into arbitrary target file\n    'dump-guest-memory' => 'root', # dump guest memory into file 'filename'.\n    # root-only: dumps into arbitrary target file\n    dumpdtb => 'root', # dump the FDT in dtb format to 'filename'\n    # root-only: starts GDB server on the host\n    gdbserver => 'root', # start gdbserver on given device (default 'tcp::1234'), stop with 'none'\n    # root-only: host information leak\n    gpa2hpa => 'Sys.Modify', # print the host physical address corresponding to a guest physical address\n    # root-only: host information leak\n    gpa2hva => 'Sys.Modify', # print the host virtual address corresponding to a guest physical address\n    # root-only: redirect TCP or UDP connections from host to guest\n    hostfwd_add => 'root', # redirect TCP or UDP connections from host to guest (requires -net user)\n    # root-only: because hostfwd_add is\n    hostfwd_remove => 'root', # remove host-to-guest TCP or UDP redirection\n    # root-only: read from IO adress space (e.g. PCI devices)\n    i => 'Sys.Modify', # I/O port read\n    # root-only: log to arbitrary target file\n    logfile => 'root', # output logs to 'filename'\n    # root-only: no guarantee there are no KVM bugs that could afffect the real CPU\n    mce => 'root', # inject a MCE on the given CPU [and broadcast to other CPUs with -b option]\n    # root-only: allows to save to arbitrary file\n    memsave => 'root', # save to disk virtual memory dump starting at 'addr' of size 'size'\n    # root-only: could specify arbitrary host, also there is 'exec' and 'file' migrations\n    migrate => 'root', # migrate to URI (using -d to not wait for completion)\n    # root-only: allows setting arbitrary URI\n    migrate_incoming => 'root', # Continue an incoming migration from an -incoming defer\n    # root-only: allows setting arbitrary URI\n    migrate_recover => 'root', # Continue a paused incoming postcopy migration\n    # root-only: because nbd_server_start is\n    nbd_server_add => 'root', # export a block device via NBD\n    # root-only: because nbd_server_start is\n    nbd_server_remove => 'root', # remove an export previously exposed via NBD\n    # root-only: start NBD server on the host\n    nbd_server_start => 'root', # serve block devices on the given host and port\n    # root-only: because nbd_server_start is\n    nbd_server_stop => 'root', # stop serving block devices using the NBD protocol\n    # root-only: add host network device\n    netdev_add => 'root', # add host network device\n    # root-only: because netdev_add is\n    netdev_del => 'root', # remove host network device\n    # root-only: no guarantee there are no KVM bugs that could afffect the real CPU\n    nmi => 'root', # inject an NMI\n    # root-only: write to IO adress space (e.g. PCI devices)\n    o => 'root', # I/O port write\n    # root-only: create arbitrary objects, e.g. serial\n    object_add => 'root', # create QOM object\n    # root-only: because object_del is\n    object_del => 'root', # destroy QOM object\n    # root-only: inject error on PCIe devices\n    pcie_aer_inject_error => 'root', # inject pcie aer error\n    # root-only: save to arbitrary file\n    pmemsave => 'root', # save to disk physical memory dump starting at 'addr' of size 'size'\n    # root-only: modify arbitrary object properties\n    'qom-set' => 'root', # set QOM property.\n    # root-only: because savevm-start is\n    'savevm-end' => 'root', # Resume VM after snaphot.\n    # root-only: save VM state to arbitrary target file\n    'savevm-start' => 'root', # Prepare for snapshot and halt VM. Save VM state to statefile.\n    # root-only: dump to arbitrary target file\n    screendump => 'root', # save screen\n    # root-only: allows specifying arbitrary target file\n    snapshot_blkdev => 'root', # initiates a live snapshot of device\n    # root-only: allows inject-nmi\n    watchdog_action => 'root', # change watchdog action\n    # root-only: saves to arbitrary target file\n    wavcapture => 'root', # capture audio to a wave file\n    # root-only: not relevant for Proxmox VE\n    'xen-event-inject' => 'root', # inject event channel\n    # root-only: not relevant for Proxmox VE\n    'xen-event-list' => 'root', # list event channel state\n\n    announce_self => 'Sys.Modify', # Trigger GARP/RARP announcements\n    backup_cancel => 'Sys.Modify', # cancel the current VM backup\n    balloon => 'Sys.Modify', # request VM to change its memory allocation (in MB)\n    block_job_cancel => 'Sys.Modify', # stop an active background block operation\n    block_job_complete => 'Sys.Modify', # stop an active background block operation\n    block_job_pause => 'Sys.Modify', # pause an active background block operation\n    block_job_resume => 'Sys.Modify', # resume a paused background block operation\n    block_job_set_speed => 'Sys.Modify', # set maximum speed for a background block operation\n    block_resize => 'Sys.Modify', # resize a block image\n    block_set_io_throttle => 'Sys.Modify', # change I/O throttle limits for a block drive\n    boot_set => 'Sys.Modify', # define new values for the boot device list\n    calc_dirty_rate => 'Sys.Modify', # start a round of guest dirty rate measurement\n    cancel_vcpu_dirty_limit => 'Sys.Modify', # cancel dirty page rate limit\n    'chardev-send-break' => 'Sys.Modify', # send a break on chardev\n    closefd => 'Sys.Modify', # close a file descriptor previously passed via SCM rights\n    commit => 'Sys.Modify', # commit changes to the disk images or backing files\n    cont => 'Sys.Modify', # resume emulation\n    c => 'Sys.Modify', # short-form of 'cont'\n    cpu => 'Sys.Modify', # set the default CPU\n    delvm => 'Sys.Modify', # delete a VM snapshot from its tag\n    eject => 'Sys.Modify', # eject a removable medium (use -f to force it)\n    exit_preconfig => 'Sys.Modify', # exit the preconfig state\n    expire_password => 'Sys.Modify', # set spice/vnc password expire-time\n    getfd => 'Sys.Modify', # receive a file descriptor via SCM rights and assign it a name\n    gva2gpa => 'Sys.Modify', # print the guest physical address corresponding to a guest virtual address\n    loadvm => 'Sys.Modify', # restore a VM snapshot from its tag\n    log => 'Sys.Modify', # activate logging of the specified items\n    migrate_cancel => 'Sys.Modify', # cancel the current VM migration\n    migrate_continue => 'Sys.Modify', # Continue migration from the given paused state\n    migrate_pause => 'Sys.Modify', # Pause an ongoing migration (postcopy-only)\n    migrate_set_capability => 'Sys.Modify', # Enable/Disable the usage of a capability for migration\n    migrate_set_parameter => 'Sys.Modify', # Set the parameter for migration\n    migrate_start_postcopy => 'Sys.Modify', # Switch the migration to postcopy mode.\n    mouse_button => 'Sys.Modify', # change mouse button state (1=L, 2=M, 4=R)\n    mouse_move => 'Sys.Modify', # send mouse move events\n    mouse_set => 'Sys.Modify', # set which mouse device receives events\n    'one-insn-per-tb' => 'Sys.Modify', # run emulation with one guest instruction per translation block\n    print => 'Sys.Modify', # print expression value (use $reg for CPU register access)\n    p => 'Sys.Modify', # alias for 'print'\n    'qemu-io' => 'Sys.Modify', # run a qemu-io command on a block device\n    # decidedly not root-only even if qom-set ist, because it is just too useful\n    'qom-get' => 'Sys.Modify', # print QOM property\n    'qom-list' => 'Sys.Modify', # list QOM properties\n    quit => 'Sys.Modify', # quit the emulator\n    q => 'Sys.Modify', # short-form of 'quit'\n    replay_break => 'Sys.Modify', # set breakpoint at the specified instruction count\n    replay_delete_break => 'Sys.Modify', # remove replay breakpoint\n    replay_seek => 'Sys.Modify', # replay execution to the specified instruction count\n    ringbuf_read => 'Sys.Modify', # Read from a ring buffer character device\n    ringbuf_write => 'Sys.Modify', # Write to a ring buffer character device\n    savevm => 'Sys.Modify', # save a VM snapshot. If no tag is provided, a new snapshot is created\n    sendkey => 'Sys.Modify', # send keys to the VM\n    set_link => 'Sys.Modify', # change the link status of a network adapter\n    set_password => 'Sys.Modify', # set spice/vnc password\n    set_vcpu_dirty_limit => 'Sys.Modify', # set dirty page rate limit\n    snapshot_blkdev_internal => 'Sys.Modify', # take an internal snapshot of device.\n    snapshot_delete_blkdev_internal => 'Sys.Modify', # delete an internal snapshot of device.\n    stopcapture => 'Sys.Modify', # stop capture\n    stop => 'Sys.Modify', # stop emulation\n    s => 'Sys.Modify', # short-form of 'stop'\n    sum => 'Sys.Modify', # compute the checksum of a memory region\n    'sync-profile' => 'Sys.Modify', # enable, disable or reset synchronization profiling.\n    system_powerdown => 'Sys.Modify', # send system power down event\n    system_reset => 'Sys.Modify', # reset the system\n    system_wakeup => 'Sys.Modify', # wakeup guest from suspend\n    'trace-event' => 'Sys.Modify', # changes status of a specific trace event\n    x => 'Sys.Modify', # virtual memory dump starting at 'addr'\n    x_colo_lost_heartbeat => 'Sys.Modify', # Tell COLO that heartbeat is lost\n    xp => 'Sys.Modify', # physical memory dump starting at 'addr'\n};\n\nsub generate_description {\n    my $cmd_by_priv = {};\n    for my $cmd (sort keys $hmp_command_perms->%*) {\n        my $priv = $hmp_command_perms->{$cmd};\n        $cmd_by_priv->{$priv} = [] if !exists($cmd_by_priv->{$priv});\n\n        push $cmd_by_priv->{$priv}->@*, $cmd;\n    }\n    my $none_cmds = delete($cmd_by_priv->{none})\n        or die \"internal error - no commands for 'none' found\";\n    my $root_only_cmds = delete($cmd_by_priv->{'root'})\n        or die \"internal error no commands for 'root' found\";\n\n    my $text = '';\n    $text .= \"The following commands do not require any additional privilege: \"\n        . join(', ', $none_cmds->@*) . \"\\n\\n\";\n\n    for my $priv (sort keys $cmd_by_priv->%*) {\n        $text .= \"The following commands require '$priv': \"\n            . join(', ', $cmd_by_priv->{$priv}->@*) . \"\\n\\n\";\n    }\n\n    $text .= \"The following commands are root-only: \" . join(', ', $root_only_cmds->@*) . \"\\n\";\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/API2/Qemu/Machine.pm",
    "content": "package PVE::API2::Qemu::Machine;\n\nuse strict;\nuse warnings;\n\nuse JSON;\n\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::RESTHandler;\nuse PVE::Tools qw(extract_param file_get_contents);\n\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Helpers qw(get_host_arch);\n\nuse base qw(PVE::RESTHandler);\n\n__PACKAGE__->register_method({\n    name => 'types',\n    path => '',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Get available QEMU/KVM machine types.\",\n    permissions => {\n        user => 'all',\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            arch => get_standard_option('pve-qm-cpu-arch', { optional => 1 }),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => 'object',\n            additionalProperties => 1,\n            properties => {\n                id => {\n                    type => 'string',\n                    description => \"Full name of machine type and version.\",\n                },\n                type => {\n                    type => 'string',\n                    enum => ['q35', 'i440fx'],\n                    description => \"The machine type.\",\n                },\n                version => {\n                    type => 'string',\n                    description => \"The machine version.\",\n                },\n                changes => {\n                    type => 'string',\n                    optional => 1,\n                    description =>\n                        'Notable changes of a version, currently only set for +pveX versions.',\n                },\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $arch = extract_param($param, 'arch') // get_host_arch();\n\n        my $supported_machine_list = eval {\n            my $raw = file_get_contents(\"/usr/share/kvm/machine-versions-$arch.json\");\n            my $machines = from_json($raw, { utf8 => 1 });\n\n            my $pve_machines = [];\n            for my $machine ($machines->@*) {\n                my $pve_machine =\n                    PVE::QemuServer::Machine::get_machine_pve_revisions($machine->{version})\n                    or next;\n\n                for my $pve_revision (sort keys $pve_machine->{revisions}->%*) {\n                    my $entry = {\n                        id => $machine->{id} . $pve_revision,\n                        type => $machine->{type},\n                        version => $machine->{version} . $pve_revision,\n                    };\n\n                    if (defined(my $changes = $pve_machine->{revisions}->{$pve_revision})) {\n                        $entry->{changes} = $changes;\n                    }\n\n                    push $pve_machines->@*, $entry;\n                }\n            }\n\n            return [\n                sort {\n                    PVE::QemuServer::Machine::machine_version_cmp($b->{id}, $a->{id})\n                } ($machines->@*, $pve_machines->@*)\n            ]; # merge & sort\n        };\n        die \"could not load supported machine versions - $@\\n\" if $@;\n        return $supported_machine_list;\n    },\n});\n\n1;\n"
  },
  {
    "path": "src/PVE/API2/Qemu/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES=Agent.pm CPU.pm CPUFlags.pm HMPPerms.pm Machine.pm\n\n.PHONY: install\ninstall:\n\tinstall -d -m 0755 $(DESTDIR)$(PERLDIR)/PVE/API2/Qemu\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/API2/Qemu/$$i; done\n"
  },
  {
    "path": "src/PVE/API2/Qemu.pm",
    "content": "package PVE::API2::Qemu;\n\nuse strict;\nuse warnings;\nuse Cwd 'abs_path';\nuse Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);\nuse Net::SSLeay;\nuse IO::Socket::IP;\nuse IO::Socket::SSL;\nuse IO::Socket::UNIX;\nuse IPC::Open3;\nuse JSON;\nuse URI::Escape;\nuse Socket qw(SOCK_STREAM);\n\nuse PVE::APIClient::LWP;\nuse PVE::CGroup;\nuse PVE::Cluster qw (cfs_read_file cfs_write_file);\nuse PVE::RRD;\nuse PVE::SafeSyslog;\nuse PVE::Tools qw(extract_param run_command);\nuse PVE::Exception qw(raise raise_param_exc raise_perm_exc);\nuse PVE::Storage;\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::RESTHandler;\nuse PVE::ReplicationConfig;\nuse PVE::GuestHelpers qw(assert_tag_permissions);\nuse PVE::GuestImport;\nuse PVE::QemuConfig;\nuse PVE::QemuServer;\nuse PVE::QemuServer::Agent;\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::BlockJob;\nuse PVE::QemuServer::Cloudinit;\nuse PVE::QemuServer::CPUConfig;\nuse PVE::QemuServer::Drive qw(checked_volume_format checked_parse_volname);\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::ImportDisk;\nuse PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Memory qw(get_current_memory);\nuse PVE::QemuServer::MetaInfo;\nuse PVE::QemuServer::Network;\nuse PVE::QemuServer::OVMF;\nuse PVE::QemuServer::PCI;\nuse PVE::QemuServer::QMPHelpers;\nuse PVE::QemuServer::RNG;\nuse PVE::QemuServer::RunState;\nuse PVE::QemuServer::USB;\nuse PVE::QemuServer::Virtiofs qw(max_virtiofs);\nuse PVE::QemuServer::DBusVMState;\nuse PVE::QemuMigrate;\nuse PVE::QemuMigrate::Helpers;\nuse PVE::RPCEnvironment;\nuse PVE::AccessControl;\nuse PVE::INotify;\nuse PVE::Network;\nuse PVE::Firewall;\nuse PVE::API2::Firewall::VM;\nuse PVE::API2::Qemu::Agent;\nuse PVE::API2::Qemu::HMPPerms;\nuse PVE::VZDump::Plugin;\nuse PVE::DataCenterConfig;\nuse PVE::ProcFSTools;\nuse PVE::SSHInfo;\nuse PVE::Replication;\nuse PVE::ReplicationState;\nuse PVE::StorageTunnel;\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Ticket;\n\nBEGIN {\n    if (!$ENV{PVE_GENERATING_DOCS}) {\n        require PVE::HA::Env::PVE2;\n        import PVE::HA::Env::PVE2;\n        require PVE::HA::Config;\n        import PVE::HA::Config;\n    }\n}\n\nuse base qw(PVE::RESTHandler);\n\nmy $opt_force_description =\n    \"Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.\";\n\nmy $resolve_cdrom_alias = sub {\n    my $param = shift;\n\n    if (my $value = $param->{cdrom}) {\n        $value .= \",media=cdrom\" if $value !~ m/media=/;\n        $param->{ide2} = $value;\n        delete $param->{cdrom};\n    }\n};\n\n# Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.\nmy $foreach_volume_with_alloc = sub {\n    my ($param, $func) = @_;\n\n    for my $opt (sort keys $param->%*) {\n        next if !PVE::QemuServer::is_valid_drivename($opt);\n\n        my $drive = PVE::QemuServer::Drive::parse_drive($opt, $param->{$opt}, 1);\n        next if !$drive;\n\n        $func->($opt, $drive);\n    }\n};\n\nmy $check_drive_param = sub {\n    my ($param, $storecfg, $extra_checks) = @_;\n\n    for my $opt (sort keys $param->%*) {\n        next if !PVE::QemuServer::is_valid_drivename($opt);\n\n        my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}, 1);\n        raise_param_exc({ $opt => \"unable to parse drive options\" }) if !$drive;\n\n        if ($drive->{'import-from'}) {\n            if ($drive->{file} !~ $PVE::QemuServer::Drive::NEW_DISK_RE || $3 != 0) {\n                raise_param_exc({\n                    $opt => \"'import-from' requires special syntax - \"\n                        . \"use <storage ID>:0,import-from=<source>\",\n                });\n            }\n\n            if ($opt eq 'efidisk0') {\n                for my $required (qw(efitype pre-enrolled-keys)) {\n                    if (!defined($drive->{$required})) {\n                        raise_param_exc({\n                            $opt => \"need to specify '$required' when using 'import-from'\",\n                        });\n                    }\n                }\n            } elsif ($opt eq 'tpmstate0') {\n                raise_param_exc({\n                    $opt => \"need to specify 'version' when using 'import-from'\" })\n                    if !defined($drive->{version});\n            }\n        }\n\n        PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);\n\n        my $volid = $drive->{file};\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n        if (\n            $storeid\n            && $volid !~ $PVE::QemuServer::Drive::NEW_DISK_RE\n            && defined($volname)\n            && $volname ne 'cloudinit'\n        ) {\n            my $vtype = (PVE::Storage::parse_volname($storecfg, $volid))[0];\n            raise_param_exc({ $opt => \"explicit 'media=cdrom' is required for iso images\" })\n                if $vtype eq 'iso' && !(defined($drive->{media}) && $drive->{media} eq 'cdrom');\n        }\n\n        $extra_checks->($drive) if $extra_checks;\n\n        $param->{$opt} = PVE::QemuServer::print_drive($drive, 1);\n    }\n};\n\nmy $check_storage_access = sub {\n    my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage, $extraction_storage)\n        = @_;\n\n    $foreach_volume_with_alloc->(\n        $settings,\n        sub {\n            my ($ds, $drive) = @_;\n\n            my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);\n\n            my $volid = $drive->{file};\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n\n            if (\n                !$volid\n                || ($volid eq 'none'\n                    || $volid eq 'cloudinit'\n                    || (defined($volname) && $volname eq 'cloudinit'))\n            ) {\n                # nothing to check\n            } elsif ($isCDROM && ($volid eq 'cdrom')) {\n                $rpcenv->check($authuser, \"/\", ['Sys.Console']);\n            } elsif (!$isCDROM && ($volid =~ $PVE::QemuServer::Drive::NEW_DISK_RE)) {\n                my $storeid = $2 || $default_storage;\n                die \"no storage ID specified (and no default storage)\\n\" if !$storeid;\n                $rpcenv->check($authuser, \"/storage/$storeid\", ['Datastore.AllocateSpace']);\n                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n                raise_param_exc({ storage => \"storage '$storeid' does not support vm images\" })\n                    if !$scfg->{content}->{images};\n            } else {\n                PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);\n                if ($storeid) {\n                    my ($vtype) = PVE::Storage::parse_volname($storecfg, $volid);\n                    raise_param_exc({ $ds => \"content type needs to be 'images' or 'iso'\" })\n                        if $vtype ne 'images' && $vtype ne 'iso';\n                }\n            }\n\n            if (my $src_image = $drive->{'import-from'}) {\n                my $src_vmid;\n                my ($storeid) = PVE::Storage::parse_volume_id($src_image, 1);\n                if ($storeid) { # PVE-managed volume\n                    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n                    (my $vtype, undef, $src_vmid, undef, undef, undef, my $fmt) =\n                        checked_parse_volname($storecfg, $src_image);\n\n                    raise_param_exc(\n                        {\n                            $ds =>\n                                \"$src_image has wrong type '$vtype' - needs to be 'images' or 'import'\",\n                        },\n                    ) if $vtype ne 'images' && $vtype ne 'import';\n\n                    if (PVE::QemuServer::Helpers::needs_extraction($vtype, $fmt)) {\n                        my $extraction_scfg =\n                            defined($extraction_storage)\n                            ? PVE::Storage::storage_config($storecfg, $extraction_storage)\n                            : $scfg;\n                        my $extraction_param =\n                            defined($extraction_storage) ? 'import-working-storage' : $ds;\n                        my $extraction_id = $extraction_storage // $storeid;\n\n                        if (\n                            !$extraction_scfg->{content}->{images} || !$extraction_scfg->{path}\n                        ) {\n                            raise_param_exc({\n                                $extraction_param =>\n                                    \"import working storage '$extraction_id' does not support\"\n                                    . \" 'images' content type or is not file based.\",\n                            });\n                        }\n                        $rpcenv->check(\n                            $authuser,\n                            \"/storage/$extraction_id\",\n                            ['Datastore.AllocateSpace'],\n                        );\n                    }\n                }\n\n                if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()\n                    $rpcenv->check($authuser, \"/vms/${src_vmid}\", ['VM.Clone']);\n                } else {\n                    PVE::Storage::check_volume_access(\n                        $rpcenv, $authuser, $storecfg, $vmid, $src_image,\n                    );\n                }\n            }\n        },\n    );\n\n    $rpcenv->check(\n        $authuser,\n        \"/storage/$settings->{vmstatestorage}\",\n        ['Datastore.AllocateSpace'],\n    ) if defined($settings->{vmstatestorage});\n};\n\nmy $check_storage_access_clone = sub {\n    my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;\n\n    my $sharedvm = 1;\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);\n\n            my $volid = $drive->{file};\n\n            return if !$volid || $volid eq 'none';\n\n            if ($isCDROM) {\n                if ($volid eq 'cdrom') {\n                    $rpcenv->check($authuser, \"/\", ['Sys.Console']);\n                } else {\n                    # we simply allow access\n                    my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);\n                    my $scfg = PVE::Storage::storage_config($storecfg, $sid);\n                    $sharedvm = 0 if !$scfg->{shared};\n\n                }\n            } else {\n                my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);\n                my $scfg = PVE::Storage::storage_config($storecfg, $sid);\n                $sharedvm = 0 if !$scfg->{shared};\n\n                $sid = $storage if $storage;\n                $rpcenv->check($authuser, \"/storage/$sid\", ['Datastore.AllocateSpace']);\n            }\n        },\n    );\n\n    $rpcenv->check($authuser, \"/storage/$conf->{vmstatestorage}\", ['Datastore.AllocateSpace'])\n        if defined($conf->{vmstatestorage});\n\n    return $sharedvm;\n};\n\nmy $check_storage_access_migrate = sub {\n    my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;\n\n    PVE::Storage::storage_check_enabled($storecfg, $storage, $node);\n\n    $rpcenv->check($authuser, \"/storage/$storage\", ['Datastore.AllocateSpace']);\n\n    my $scfg = PVE::Storage::storage_config($storecfg, $storage);\n    die \"storage '$storage' does not support vm images\\n\"\n        if !$scfg->{content}->{images};\n};\n\nmy $import_from_volid = sub {\n    my ($storecfg, $src_volid, $dest_info, $vollist) = @_;\n\n    die \"could not get size of $src_volid\\n\"\n        if !PVE::Storage::volume_size_info($storecfg, $src_volid, 10);\n\n    die \"cannot import from cloudinit disk\\n\"\n        if PVE::QemuServer::Drive::drive_is_cloudinit({ file => $src_volid });\n\n    my $src_vmid = (PVE::Storage::parse_volname($storecfg, $src_volid))[2];\n\n    my $src_vm_state = sub {\n        my $exists = $src_vmid && PVE::Cluster::get_vmlist()->{ids}->{$src_vmid} ? 1 : 0;\n\n        my $runs = 0;\n        if ($exists) {\n            eval { PVE::QemuConfig::assert_config_exists_on_node($src_vmid); };\n            die \"owner VM $src_vmid not on local node\\n\" if $@;\n            $runs = PVE::QemuServer::Helpers::vm_running_locally($src_vmid) || 0;\n        }\n\n        return ($exists, $runs);\n    };\n\n    my ($src_vm_exists, $running) = $src_vm_state->();\n\n    die \"cannot import from '$src_volid' - full clone feature is not supported\\n\"\n        if !PVE::Storage::volume_has_feature($storecfg, 'copy', $src_volid, undef, $running);\n\n    my $clonefn = sub {\n        my ($src_vm_exists_now, $running_now) = $src_vm_state->();\n\n        die \"owner VM $src_vmid changed state unexpectedly\\n\"\n            if $src_vm_exists_now != $src_vm_exists || $running_now != $running;\n\n        my $src_conf = $src_vm_exists_now ? PVE::QemuConfig->load_config($src_vmid) : {};\n\n        my $src_drive = { file => $src_volid };\n        my $src_drivename;\n        PVE::QemuConfig->foreach_volume(\n            $src_conf,\n            sub {\n                my ($ds, $drive) = @_;\n\n                return if $src_drivename;\n\n                if ($drive->{file} eq $src_volid) {\n                    $src_drive = $drive;\n                    $src_drivename = $ds;\n                }\n            },\n        );\n\n        my $source_info = {\n            vmid => $src_vmid,\n            running => $running_now,\n            drivename => $src_drivename,\n            drive => $src_drive,\n            snapname => undef,\n        };\n\n        my ($src_storeid) = PVE::Storage::parse_volume_id($src_volid);\n\n        my $fs_freeze = PVE::QemuServer::Agent::should_fs_freeze($src_conf);\n\n        return PVE::QemuServer::clone_disk(\n            $storecfg,\n            $source_info,\n            $dest_info,\n            1,\n            $vollist,\n            undef,\n            undef,\n            $fs_freeze,\n            PVE::Storage::get_bandwidth_limit('clone', [$src_storeid, $dest_info->{storage}]),\n        );\n    };\n\n    my $cloned;\n    if ($running) {\n        $cloned = PVE::QemuConfig->lock_config_full($src_vmid, 30, $clonefn);\n    } elsif ($src_vmid) {\n        $cloned = PVE::QemuConfig->lock_config_shared($src_vmid, 30, $clonefn);\n    } else {\n        $cloned = $clonefn->();\n    }\n\n    return $cloned->@{qw(file size)};\n};\n\nmy sub prohibit_tpm_version_change {\n    my ($old, $new) = @_;\n\n    return if !$old || !$new;\n\n    my $old_drive = PVE::QemuServer::parse_drive('tpmstate0', $old);\n    my $new_drive = PVE::QemuServer::parse_drive('tpmstate0', $new);\n\n    return if $old_drive->{file} ne $new_drive->{file};\n\n    my $old_version = $old_drive->{version} // 'v1.2';\n    my $new_version = $new_drive->{version} // 'v1.2';\n\n    die \"cannot change TPM state version after creation\\n\" if $old_version ne $new_version;\n\n    return;\n}\n\n# Note: $pool is only needed when creating a VM, because pool permissions\n# are automatically inherited if VM already exists inside a pool.\nmy sub create_disks : prototype($$$$$$$$$$$) {\n    my (\n        $rpcenv,\n        $authuser,\n        $conf,\n        $arch,\n        $storecfg,\n        $vmid,\n        $pool,\n        $settings,\n        $default_storage,\n        $is_live_import,\n        $extraction_storage,\n    ) = @_;\n\n    my $vollist = [];\n\n    my $res = {};\n\n    my $live_import_mapping = {};\n\n    my $code = sub {\n        my ($ds, $disk) = @_;\n\n        my $volid = $disk->{file};\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n\n        if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {\n            delete $disk->{size};\n            $res->{$ds} = PVE::QemuServer::print_drive($disk);\n        } elsif (defined($volname) && $volname eq 'cloudinit') {\n            $storeid = $storeid // $default_storage;\n            die \"no storage ID specified (and no default storage)\\n\" if !$storeid;\n\n            if (\n                my $ci_key =\n                PVE::QemuConfig->has_cloudinit($conf, $ds)\n                || PVE::QemuConfig->has_cloudinit($conf->{pending} || {}, $ds)\n                || PVE::QemuConfig->has_cloudinit($res, $ds)\n            ) {\n                die \"$ds - cloud-init drive is already attached at '$ci_key'\\n\";\n            }\n\n            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n            my $name = \"vm-$vmid-cloudinit\";\n\n            my $fmt = undef;\n            if ($scfg->{path}) {\n                $fmt = $disk->{format} // \"qcow2\";\n                $name .= \".$fmt\";\n            } else {\n                $fmt = $disk->{format} // \"raw\";\n            }\n\n            # Initial disk created with 4 MB and aligned to 4MB on regeneration\n            my $ci_size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE;\n            my $volid =\n                PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, $name, $ci_size / 1024);\n            $disk->{file} = $volid;\n            $disk->{media} = 'cdrom';\n            push @$vollist, $volid;\n            delete $disk->{format}; # no longer needed\n            $res->{$ds} = PVE::QemuServer::print_drive($disk);\n            print \"$ds: successfully created disk '$res->{$ds}'\\n\";\n        } elsif ($volid =~ $PVE::QemuServer::Drive::NEW_DISK_RE) {\n            my ($storeid, $size) = ($2 || $default_storage, $3);\n            die \"no storage ID specified (and no default storage)\\n\" if !$storeid;\n\n            $size = PVE::Tools::convert_size($size, 'gb' => 'kb'); # vdisk_alloc uses kb\n\n            my $live_import = $is_live_import && $ds ne 'efidisk0';\n            my $needs_creation = 1;\n\n            if (my $source = delete $disk->{'import-from'}) {\n                my $dst_volid;\n\n                $needs_creation = $live_import;\n\n                my ($source_storage, $source_volid) = PVE::Storage::parse_volume_id($source, 1);\n\n                if ($source_storage) { # PVE-managed volume\n                    my ($vtype, $source_format) =\n                        (checked_parse_volname($storecfg, $source))[0, 6];\n                    my $needs_extraction =\n                        PVE::QemuServer::Helpers::needs_extraction($vtype, $source_format);\n                    my $untrusted = $vtype eq 'import' ? 1 : 0;\n                    if ($needs_extraction) {\n                        print \"extracting $source\\n\";\n                        my $extracted_volid = PVE::GuestImport::extract_disk_from_import_file(\n                            $source, $vmid, $extraction_storage,\n                        );\n                        print \"finished extracting to $extracted_volid\\n\";\n                        push @$vollist, $extracted_volid;\n                        $source = $extracted_volid;\n                        $source_format = checked_volume_format($storecfg, $source);\n\n                        my (undef, undef, undef, $parent) =\n                            PVE::Storage::volume_size_info($storecfg, $source);\n                        die\n                            \"importing from extracted images with backing file ($parent) not allowed\\n\"\n                            if $parent;\n                    }\n\n                    if ($live_import && $ds ne 'efidisk0') {\n                        my $path = PVE::Storage::path($storecfg, $source)\n                            or die \"failed to get a path for '$source'\\n\";\n                        # check potentially untrusted image file for import vtype\n                        $size =\n                            PVE::Storage::file_size_info($path, undef, $source_format, $untrusted);\n                        die \"could not get file size of $path\\n\" if !$size;\n                        $live_import_mapping->{$ds} = {\n                            path => $path,\n                            format => $source_format,\n                            volid => $source,\n                        };\n                        $live_import_mapping->{$ds}->{'delete-after-finish'} = $source\n                            if $needs_extraction;\n                    } else {\n                        # check potentially untrusted image file for import vtype\n                        if ($untrusted) {\n                            my $path = PVE::Storage::path($storecfg, $source);\n                            PVE::Storage::file_size_info($path, undef, $source_format, 1);\n                        }\n\n                        my $dest_info = {\n                            vmid => $vmid,\n                            drivename => $ds,\n                            storage => $storeid,\n                            format => $disk->{format},\n                        };\n\n                        $dest_info->{efisize} = PVE::QemuServer::get_efivars_size($conf, $disk)\n                            if $ds eq 'efidisk0';\n\n                        eval {\n                            ($dst_volid, $size) =\n                                $import_from_volid->($storecfg, $source, $dest_info, $vollist);\n\n                            # remove extracted volumes after importing\n                            if ($needs_extraction) {\n                                PVE::Storage::vdisk_free($storecfg, $source);\n                                print \"cleaned up extracted image $source\\n\";\n                            }\n                            @$vollist = grep { $_ ne $source } @$vollist;\n                        };\n                        die \"cannot import from '$source' - $@\" if $@;\n                    }\n                } else {\n                    $source = PVE::Storage::abs_filesystem_path($storecfg, $source, 1);\n                    # check potentially untrusted image file!\n                    ($size, my $source_format) =\n                        PVE::Storage::file_size_info($source, undef, 'auto-detect', 1);\n                    die \"could not get file size of $source\\n\" if !$size;\n\n                    if ($live_import && $ds ne 'efidisk0') {\n                        $live_import_mapping->{$ds} = {\n                            path => $source,\n                            format => $source_format,\n                            volid => $source,\n                        };\n                    } else {\n                        (undef, $dst_volid) = PVE::QemuServer::ImportDisk::do_import(\n                            $source,\n                            $size,\n                            $vmid,\n                            $storeid,\n                            {\n                                drive_name => $ds,\n                                format => $disk->{format},\n                                'skip-config-update' => 1,\n                            },\n                        );\n\n                        # change imported disk to a base volume in case the VM is a template\n                        $dst_volid = PVE::Storage::vdisk_create_base($storecfg, $dst_volid)\n                            if PVE::QemuConfig->is_template($conf);\n\n                        push @$vollist, $dst_volid;\n                    }\n                }\n\n                if ($needs_creation) {\n                    $size = PVE::Tools::convert_size($size, 'b' => 'kb'); # vdisk_alloc uses kb\n                } else {\n                    $disk->{file} = $dst_volid;\n                    $disk->{size} = $size;\n                    delete $disk->{format}; # no longer needed\n                    $res->{$ds} = PVE::QemuServer::print_drive($disk);\n                }\n            }\n\n            if ($needs_creation) {\n                my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);\n                my $fmt = $disk->{format} || $defformat;\n\n                my $volid;\n                if ($ds eq 'efidisk0') {\n                    my $smm = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n\n                    my $cvm_type = PVE::QemuServer::CPUConfig::get_cvm_type($conf);\n                    die\n                        \"SEV-SNP uses consolidated read-only firmware and does not require an EFI disk\\n\"\n                        if $cvm_type && $cvm_type eq 'snp';\n\n                    ($volid, $size) = PVE::QemuServer::OVMF::create_efidisk(\n                        $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm, $cvm_type,\n                    );\n                } elsif ($ds eq 'tpmstate0') {\n                    # A fixed size is used for TPM state volumes\n                    $size = PVE::Tools::convert_size(\n                        PVE::QemuServer::Drive::TPMSTATE_DISK_SIZE,\n                        'b' => 'kb',\n                    );\n                    $volid =\n                        PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $size);\n                } else {\n                    $volid =\n                        PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $size);\n                }\n\n                # change created disk to a base volume in case the VM is a template\n                $volid = PVE::Storage::vdisk_create_base($storecfg, $volid)\n                    if PVE::QemuConfig->is_template($conf);\n\n                push @$vollist, $volid;\n                $disk->{file} = $volid;\n                $disk->{size} = PVE::Tools::convert_size($size, 'kb' => 'b');\n                delete $disk->{format}; # no longer needed\n                $res->{$ds} = PVE::QemuServer::print_drive($disk);\n            }\n\n            print \"$ds: successfully created disk '$res->{$ds}'\\n\";\n        } else {\n            PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);\n            if ($storeid) {\n                my ($vtype, $volume_format) = (checked_parse_volname($storecfg, $volid))[0, 6];\n\n                die \"cannot use volume $volid - content type needs to be 'images' or 'iso'\"\n                    if $vtype ne 'images' && $vtype ne 'iso';\n\n                # TODO PVE 9 - consider disallowing setting an explicit format for managed volumes.\n                if ($disk->{format} && $disk->{format} ne $volume_format) {\n                    die\n                        \"drive '$ds' - volume '$volid' - 'format=$disk->{format}' option different\"\n                        . \" from storage format '$volume_format'\\n\";\n                }\n\n                if (PVE::QemuServer::Drive::drive_is_cloudinit($disk)) {\n                    if (\n                        my $ci_key =\n                        PVE::QemuConfig->has_cloudinit($conf, $ds)\n                        || PVE::QemuConfig->has_cloudinit($conf->{pending} || {}, $ds)\n                        || PVE::QemuConfig->has_cloudinit($res, $ds)\n                    ) {\n                        die \"$ds - cloud-init drive is already attached at '$ci_key'\\n\";\n                    }\n                }\n            }\n\n            PVE::Storage::activate_volumes($storecfg, [$volid]) if $storeid;\n\n            my $size = PVE::Storage::volume_size_info($storecfg, $volid);\n            die \"volume $volid does not exist\\n\" if !$size;\n            $disk->{size} = $size;\n\n            $res->{$ds} = PVE::QemuServer::print_drive($disk);\n        }\n    };\n\n    eval { $foreach_volume_with_alloc->($settings, $code); };\n\n    # free allocated images on error\n    if (my $err = $@) {\n        syslog('err', \"VM $vmid creating disks failed\");\n        foreach my $volid (@$vollist) {\n            eval { PVE::Storage::vdisk_free($storecfg, $volid); };\n            warn $@ if $@;\n        }\n        die $err;\n    }\n\n    # don't return empty import mappings\n    $live_import_mapping = undef if !%$live_import_mapping;\n\n    return ($vollist, $res, $live_import_mapping);\n}\n\nmy $check_cpu_model_access = sub {\n    my ($rpcenv, $authuser, $new, $existing) = @_;\n\n    return if !defined($new->{cpu});\n\n    my $cpu = PVE::JSONSchema::check_format('pve-vm-cpu-conf', $new->{cpu});\n    return if !$cpu || !$cpu->{cputype}; # always allow default\n    my $cputype = $cpu->{cputype};\n\n    if ($existing && $existing->{cpu}) {\n        # changing only other settings doesn't require permissions for CPU model\n        my $existingCpu = PVE::JSONSchema::check_format('pve-vm-cpu-conf', $existing->{cpu});\n        return if $existingCpu->{cputype} eq $cputype;\n    }\n\n    if (PVE::QemuServer::CPUConfig::is_custom_model($cputype)) {\n        $rpcenv->check($authuser, \"/nodes\", ['Sys.Audit']);\n    }\n};\n\n# The top-most snapshot for a FUSE-exported TPM state cannot be removed live, because exporting\n# unshares the 'resize' permission, which would be required by both 'block-commit' and\n# 'block-stream'.\nmy sub assert_tpm_snapshot_delete_possible {\n    my ($vmid, $conf, $snap_conf, $snap_name) = @_;\n\n    return if !$conf->{tpmstate0};\n    return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    my $drive = PVE::QemuServer::Drive::parse_drive('tpmstate0', $conf->{tpmstate0});\n    my $volid = $drive->{file};\n    my $storecfg = PVE::Storage::config();\n\n    return if ($conf->{parent} // '') ne $snap_name; # allowed if not top-most snapshot\n\n    return if !$snap_conf->{tpmstate0};\n    my $snap_drive = PVE::QemuServer::Drive::parse_drive('tpmstate0', $snap_conf->{tpmstate0});\n    return if $volid ne $snap_drive->{file};\n\n    my $format = PVE::QemuServer::Drive::checked_volume_format($storecfg, $volid);\n    my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);\n    if ($storeid && $format eq 'qcow2') {\n        my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n        if ($scfg && $scfg->{'snapshot-as-volume-chain'}) {\n            die \"top-most snapshot of TPM state '$volid' on storage with 'snapshot-as-volume-chain'\"\n                . \" cannot be removed while the VM is running.\\n\";\n        }\n    }\n}\n\nmy $cpuoptions = {\n    'cores' => 1,\n    'cpu' => 1,\n    'runningcpu' => 1,\n    'cpulimit' => 1,\n    'cpuunits' => 1,\n    'numa' => 1,\n    'smp' => 1,\n    'sockets' => 1,\n    'vcpus' => 1,\n};\n\nmy $memoryoptions = {\n    'memory' => 1,\n    'balloon' => 1,\n    'shares' => 1,\n    'allow-ksm' => 1,\n};\n\nmy $hwtypeoptions = {\n    'acpi' => 1,\n    'hotplug' => 1,\n    'kvm' => 1,\n    'machine' => 1,\n    'runningmachine' => 1,\n    'scsihw' => 1,\n    'smbios1' => 1,\n    'tablet' => 1,\n    'vga' => 1,\n    'watchdog' => 1,\n    'audio0' => 1,\n    'rng0' => 1,\n};\n\nmy $generaloptions = {\n    'agent' => 1,\n    'autostart' => 1,\n    'bios' => 1,\n    'description' => 1,\n    'keyboard' => 1,\n    'localtime' => 1,\n    'migrate_downtime' => 1,\n    'migrate_speed' => 1,\n    'name' => 1,\n    'onboot' => 1,\n    'ostype' => 1,\n    'protection' => 1,\n    'reboot' => 1,\n    'startdate' => 1,\n    'startup' => 1,\n    'tdf' => 1,\n    'template' => 1,\n};\n\nmy $vmpoweroptions = {\n    'freeze' => 1,\n};\n\nmy $diskoptions = {\n    'boot' => 1,\n    'bootdisk' => 1,\n    'vmstatestorage' => 1,\n};\n\nmy $cloudinitoptions = {\n    cicustom => 1,\n    cipassword => 1,\n    citype => 1,\n    ciuser => 1,\n    ciupgrade => 1,\n    nameserver => 1,\n    searchdomain => 1,\n    sshkeys => 1,\n};\n\nmy $check_vm_create_serial_perm = sub {\n    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    foreach my $opt (keys %{$param}) {\n        next if $opt !~ m/^serial\\d+$/;\n\n        if ($param->{$opt} eq 'socket') {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);\n        } else {\n            die \"only root can set '$opt' config for real devices\\n\";\n        }\n    }\n\n    return 1;\n};\n\nmy sub check_usb_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);\n\n    my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $value);\n    if ($device->{host}) {\n        if ($device->{host} =~ m/^spice$/i) {\n            # already checked generic permission above\n        } else {\n            die \"only root can set '$opt' config for real devices\\n\";\n        }\n    } elsif ($device->{mapping}) {\n        $rpcenv->check_full($authuser, \"/mapping/usb/$device->{mapping}\", ['Mapping.Use']);\n    } else {\n        die \"either 'host' or 'mapping' must be set.\\n\";\n    }\n\n    return 1;\n}\n\nmy sub check_vm_create_usb_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    foreach my $opt (keys %{$param}) {\n        next if $opt !~ m/^usb\\d+$/;\n        check_usb_perm($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});\n    }\n\n    return 1;\n}\n\nmy sub check_hostpci_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $value);\n    if ($device->{host}) {\n        die \"only root can set '$opt' config for non-mapped devices\\n\";\n    } elsif ($device->{mapping}) {\n        $rpcenv->check_full($authuser, \"/mapping/pci/$device->{mapping}\", ['Mapping.Use']);\n        $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);\n    } else {\n        die \"either 'host' or 'mapping' must be set.\\n\";\n    }\n\n    return 1;\n}\n\nmy sub check_vm_create_hostpci_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    foreach my $opt (keys %{$param}) {\n        next if $opt !~ m/^hostpci\\d+$/;\n        check_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});\n    }\n\n    return 1;\n}\n\nmy sub check_rng_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);\n\n    my $device = PVE::JSONSchema::parse_property_string('pve-qm-rng', $value);\n    if ($device->{source} && $device->{source} eq '/dev/hwrng') {\n        $rpcenv->check_full($authuser, \"/mapping/hwrng\", ['Mapping.Use']);\n    }\n\n    return 1;\n}\n\nmy sub check_dir_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);\n\n    my $virtiofs = PVE::JSONSchema::parse_property_string('pve-qm-virtiofs', $value);\n    $rpcenv->check_full($authuser, \"/mapping/dir/$virtiofs->{dirid}\", ['Mapping.Use']);\n\n    return 1;\n}\n\nmy sub check_vm_create_dir_perm {\n    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    for (my $i = 0; $i < max_virtiofs(); $i++) {\n        my $opt = \"virtiofs$i\";\n        next if !$param->{$opt};\n        check_dir_perm($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});\n    }\n\n    return 1;\n}\n\nmy $check_vm_modify_config_perm = sub {\n    my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    foreach my $opt (@$key_list) {\n        # some checks (e.g., disk, serial port, usb) need to be done somewhere\n        # else, as there the permission can be value dependent\n        next if PVE::QemuServer::is_valid_drivename($opt);\n        next if $opt eq 'cdrom';\n        next if $opt =~ m/^(?:unused|serial|usb|hostpci|virtiofs)\\d+$/;\n        next if $opt eq 'tags';\n\n        if ($cpuoptions->{$opt} || $opt =~ m/^numa\\d+$/) {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);\n        } elsif ($memoryoptions->{$opt}) {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);\n        } elsif ($hwtypeoptions->{$opt}) {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);\n        } elsif ($generaloptions->{$opt}) {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);\n            # special case for startup since it changes host behaviour\n            if ($opt eq 'startup') {\n                $rpcenv->check_full($authuser, \"/\", ['Sys.Modify']);\n            }\n        } elsif ($vmpoweroptions->{$opt}) {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);\n        } elsif ($diskoptions->{$opt}) {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);\n        } elsif ($opt =~ m/^net\\d+$/ || $opt eq 'running-nets-host-mtu') {\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);\n        } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\\d+$/) {\n            $rpcenv->check_vm_perm(\n                $authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1,\n            );\n        } elsif ($opt eq 'vmstate') {\n            # the user needs Disk and PowerMgmt privileges to change the vmstate\n            # also needs privileges on the storage, that will be checked later\n            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt']);\n        } else {\n            # catches args, lock, etc.\n            # new options will be checked here\n            die \"only root can set '$opt' config\\n\";\n        }\n    }\n\n    return 1;\n};\n\nsub assert_scsi_feature_compatibility {\n    my ($opt, $conf, $storecfg, $drive_attributes) = @_;\n\n    my $drive = PVE::QemuServer::Drive::parse_drive($opt, $drive_attributes, 1);\n\n    my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf);\n    my $machine_version = PVE::QemuServer::Machine::extract_version(\n        $machine_type,\n        PVE::QemuServer::Helpers::kvm_user_version(),\n    );\n    my $drivetype =\n        PVE::QemuServer::Drive::get_scsi_device_type($drive, $storecfg, $machine_version);\n\n    if ($drivetype ne 'hd' && $drivetype ne 'cd') {\n        if ($drive->{product}) {\n            raise_param_exc({\n                $opt => \"Passing of product information is only supported for 'scsi-hd' and \"\n                    . \"'scsi-cd' devices (e.g. not pass-through).\",\n            });\n        }\n        if ($drive->{vendor}) {\n            raise_param_exc({\n                $opt => \"Passing of vendor information is only supported for 'scsi-hd' and \"\n                    . \"'scsi-cd' devices (e.g. not pass-through).\",\n            });\n        }\n    }\n}\n\n__PACKAGE__->register_method({\n    name => 'vmlist',\n    path => '',\n    method => 'GET',\n    description => \"Virtual machine index (per node).\",\n    permissions => {\n        description => \"Only list VMs where you have VM.Audit permissions on /vms/<vmid>.\",\n        user => 'all',\n    },\n    proxyto => 'node',\n    protected => 1, # qemu pid files are only readable by root\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            full => {\n                type => 'boolean',\n                optional => 1,\n                description => \"Determine the full status of active VMs.\",\n            },\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => \"object\",\n            properties => $PVE::QemuServer::vmstatus_return_properties,\n        },\n        links => [{ rel => 'child', href => \"{vmid}\" }],\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $vmstatus = PVE::QemuServer::vmstatus(undef, $param->{full});\n\n        my $res = [];\n        foreach my $vmid (keys %$vmstatus) {\n            next if !$rpcenv->check($authuser, \"/vms/$vmid\", ['VM.Audit'], 1);\n\n            my $data = $vmstatus->{$vmid};\n            push @$res, $data;\n        }\n\n        return $res;\n    },\n});\n\nmy $classify_restore_archive = sub {\n    my ($storecfg, $archive) = @_;\n\n    my ($archive_storeid, $archive_volname) = PVE::Storage::parse_volume_id($archive, 1);\n\n    my $res = {};\n\n    if (defined($archive_storeid)) {\n        my $scfg = PVE::Storage::storage_config($storecfg, $archive_storeid);\n        $res->{volid} = $archive;\n        if ($scfg->{type} eq 'pbs') {\n            $res->{type} = 'pbs';\n            return $res;\n        }\n        if (PVE::Storage::storage_has_feature($storecfg, $archive_storeid, 'backup-provider')) {\n            my $log_function = sub {\n                my ($log_level, $message) = @_;\n                my $prefix = $log_level eq 'err' ? 'ERROR' : uc($log_level);\n                print \"$prefix: $message\\n\";\n            };\n            my $backup_provider = PVE::Storage::new_backup_provider(\n                $storecfg, $archive_storeid, $log_function,\n            );\n\n            $res->{type} = 'external';\n            $res->{'backup-provider'} = $backup_provider;\n            return $res;\n        }\n    }\n    my $path = PVE::Storage::abs_filesystem_path($storecfg, $archive);\n    $res->{type} = 'file';\n    $res->{path} = $path;\n    return $res;\n};\n\n__PACKAGE__->register_method({\n    name => 'create_vm',\n    path => '',\n    method => 'POST',\n    description => \"Create or restore a virtual machine.\",\n    permissions => {\n        description =>\n            \"You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. \"\n            . \"For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. \"\n            . \"If you create disks you need 'Datastore.AllocateSpace' on any used storage.\"\n            . \"If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.\",\n        user => 'all', # check inside\n    },\n    protected => 1,\n    proxyto => 'node',\n    parameters => {\n        additionalProperties => 0,\n        properties => PVE::QemuServer::json_config_properties(\n            {\n                node => get_standard_option('pve-node'),\n                vmid => get_standard_option(\n                    'pve-vmid',\n                    { completion => \\&PVE::Cluster::complete_next_vmid },\n                ),\n                archive => {\n                    description =>\n                        \"The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.\",\n                    type => 'string',\n                    optional => 1,\n                    maxLength => 255,\n                    completion => \\&PVE::QemuServer::complete_backup_archives,\n                },\n                storage => get_standard_option(\n                    'pve-storage-id',\n                    {\n                        description => \"Default storage.\",\n                        optional => 1,\n                        completion => \\&PVE::QemuServer::complete_storage,\n                    },\n                ),\n                force => {\n                    optional => 1,\n                    type => 'boolean',\n                    description => \"Allow to overwrite existing VM.\",\n                    requires => 'archive',\n                },\n                unique => {\n                    optional => 1,\n                    type => 'boolean',\n                    description => \"Assign a unique random ethernet address.\",\n                    requires => 'archive',\n                },\n                'live-restore' => {\n                    optional => 1,\n                    type => 'boolean',\n                    description =>\n                        \"Start the VM immediately while importing or restoring in the background.\",\n                },\n                pool => {\n                    optional => 1,\n                    type => 'string',\n                    format => 'pve-poolid',\n                    description => \"Add the VM to the specified pool.\",\n                },\n                bwlimit => {\n                    description => \"Override I/O bandwidth limit (in KiB/s).\",\n                    optional => 1,\n                    type => 'integer',\n                    minimum => '0',\n                    default => 'restore limit from datacenter or storage config',\n                },\n                start => {\n                    optional => 1,\n                    type => 'boolean',\n                    default => 0,\n                    description => \"Start VM after it was created successfully.\",\n                },\n                'ha-managed' => {\n                    optional => 1,\n                    type => 'boolean',\n                    default => 0,\n                    description => \"Add the VM as a HA resource after it was created.\",\n                },\n                'import-working-storage' => get_standard_option(\n                    'pve-storage-id',\n                    {\n                        description =>\n                            \"A file-based storage with 'images' content-type enabled, which\"\n                            . \" is used as an intermediary extraction storage during import. Defaults to\"\n                            . \" the source storage.\",\n                        optional => 1,\n                        completion => \\&PVE::QemuServer::complete_storage,\n                    },\n                ),\n            },\n            1, # with_disk_alloc\n        ),\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n\n        my $archive = extract_param($param, 'archive');\n        my $is_restore = !!$archive;\n\n        my $bwlimit = extract_param($param, 'bwlimit');\n        my $force = extract_param($param, 'force');\n        my $pool = extract_param($param, 'pool');\n        my $start_after_create = extract_param($param, 'start');\n        my $ha_managed = extract_param($param, 'ha-managed');\n        my $storage = extract_param($param, 'storage');\n        my $unique = extract_param($param, 'unique');\n        my $live_restore = extract_param($param, 'live-restore');\n        my $extraction_storage = extract_param($param, 'import-working-storage');\n\n        if (defined(my $ssh_keys = $param->{sshkeys})) {\n            $ssh_keys = URI::Escape::uri_unescape($ssh_keys);\n            PVE::Tools::validate_ssh_public_keys($ssh_keys);\n        }\n\n        $param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})\n            if defined($param->{cpuunits}); # clamp value depending on cgroup version\n\n        PVE::Cluster::check_cfs_quorum();\n\n        my $filename = PVE::QemuConfig->config_file($vmid);\n        my $storecfg = PVE::Storage::config();\n\n        if (defined($pool)) {\n            $rpcenv->check_pool_exist($pool);\n        }\n\n        $rpcenv->check($authuser, \"/storage/$storage\", ['Datastore.AllocateSpace'])\n            if defined($storage);\n\n        $rpcenv->check($authuser, \"/\", ['Sys.Console']) if $ha_managed;\n\n        if ($rpcenv->check($authuser, \"/vms/$vmid\", ['VM.Allocate'], 1)) {\n            # OK\n        } elsif ($pool && $rpcenv->check($authuser, \"/pool/$pool\", ['VM.Allocate'], 1)) {\n            # OK\n        } elsif (\n            $archive\n            && $force\n            && (-f $filename)\n            && $rpcenv->check($authuser, \"/vms/$vmid\", ['VM.Backup'], 1)\n        ) {\n            # OK: user has VM.Backup permissions and wants to restore an existing VM\n        } else {\n            raise_perm_exc();\n        }\n\n        if ($archive) {\n            for my $opt (sort keys $param->%*) {\n                if (PVE::QemuServer::Drive::is_valid_drivename($opt)) {\n                    raise_param_exc({ $opt => \"option conflicts with option 'archive'\" });\n                }\n            }\n\n            if ($archive eq '-') {\n                die \"pipe requires cli environment\\n\" if $rpcenv->{type} ne 'cli';\n                $archive = { type => 'pipe' };\n            } else {\n                PVE::Storage::check_volume_access(\n                    $rpcenv,\n                    $authuser,\n                    $storecfg,\n                    $vmid,\n                    $archive,\n                    'backup',\n                );\n\n                $archive = $classify_restore_archive->($storecfg, $archive);\n            }\n        }\n\n        if (scalar(keys $param->%*) > 0) {\n            &$resolve_cdrom_alias($param);\n\n            &$check_storage_access(\n                $rpcenv, $authuser, $storecfg, $vmid, $param, $storage, $extraction_storage,\n            );\n\n            &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [keys %$param]);\n\n            &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);\n            check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);\n            check_vm_create_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $param);\n            check_rng_perm($rpcenv, $authuser, $vmid, $pool, 'rng0', $param->{rng0})\n                if $param->{rng0};\n            check_vm_create_dir_perm($rpcenv, $authuser, $vmid, $pool, $param);\n\n            PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $param);\n            &$check_cpu_model_access($rpcenv, $authuser, $param);\n\n            $check_drive_param->($param, $storecfg);\n\n            PVE::QemuServer::Network::add_random_macs($param);\n        }\n\n        my $emsg = $is_restore ? \"unable to restore VM $vmid -\" : \"unable to create VM $vmid -\";\n\n        eval { PVE::QemuConfig->create_and_lock_config($vmid, $force) };\n        die \"$emsg $@\" if $@;\n\n        my $restored_data = 0;\n        my $restorefn = sub {\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            PVE::QemuConfig->check_protection($conf, $emsg);\n\n            die \"$emsg vm is running\\n\" if PVE::QemuServer::check_running($vmid);\n\n            my $realcmd = sub {\n                my $restore_options = {\n                    storage => $storage,\n                    pool => $pool,\n                    unique => $unique,\n                    bwlimit => $bwlimit,\n                    live => $live_restore,\n                    override_conf => $param,\n                };\n                if (my $volid = $archive->{volid}) {\n                    # best effort, real check is after restoring!\n                    my $merged = eval {\n                        my $old_conf = PVE::Storage::extract_vzdump_config($storecfg, $volid);\n                        PVE::QemuServer::restore_merge_config(\n                            \"backup/qemu-server/$vmid.conf\",\n                            $old_conf,\n                            $param,\n                        );\n                    };\n                    if ($@) {\n                        warn \"Could not extract backed up config: $@\\n\";\n                        warn \"Skipping early checks!\\n\";\n                    } else {\n                        PVE::QemuServer::check_restore_permissions($rpcenv, $authuser, $merged);\n                    }\n                }\n                if (my $backup_provider = $archive->{'backup-provider'}) {\n                    PVE::QemuServer::restore_external_archive(\n                        $backup_provider,\n                        $archive->{volid},\n                        $vmid,\n                        $authuser,\n                        $restore_options,\n                    );\n                } elsif ($archive->{type} eq 'file' || $archive->{type} eq 'pipe') {\n                    die\n                        \"live-restore is only compatible with backup images from a Proxmox Backup Server\\n\"\n                        if $live_restore;\n                    PVE::QemuServer::restore_file_archive(\n                        $archive->{path} // '-',\n                        $vmid,\n                        $authuser,\n                        $restore_options,\n                    );\n                } elsif ($archive->{type} eq 'pbs') {\n                    PVE::QemuServer::restore_proxmox_backup_archive(\n                        $archive->{volid}, $vmid, $authuser, $restore_options,\n                    );\n                } else {\n                    die \"unknown backup archive type\\n\";\n                }\n                $restored_data = 1;\n\n                my $restored_conf = PVE::QemuConfig->load_config($vmid);\n                # Convert restored VM to template if backup was VM template\n                if (PVE::QemuConfig->is_template($restored_conf)) {\n                    warn \"Convert to template.\\n\";\n                    eval { PVE::QemuServer::template_create($vmid, $restored_conf) };\n                    warn $@ if $@;\n                }\n\n                PVE::QemuServer::Network::create_ifaces_ipams_ips($restored_conf, $vmid)\n                    if $unique;\n            };\n\n            # ensure no old replication state are exists\n            PVE::ReplicationState::delete_guest_states($vmid);\n\n            PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);\n\n            if ($start_after_create && !$live_restore) {\n                print \"Execute autostart\\n\";\n                eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) };\n                warn $@ if $@;\n            }\n\n            if ($ha_managed) {\n                my $resource_exists = PVE::HA::Config::service_is_configured(\"vm:$vmid\");\n                my $state = $start_after_create || $live_restore ? 'started' : 'stopped';\n                my $ha_cmd = $resource_exists ? 'set' : 'add';\n\n                print ucfirst(\"$ha_cmd HA resource with request-state '$state'\\n\");\n                eval { run_command(['ha-manager', $ha_cmd, \"vm:$vmid\", '--state', $state]) };\n                warn $@ if $@;\n            }\n        };\n\n        my $createfn = sub {\n            my $live_import_mapping = {};\n\n            # ensure no old replication state are exists\n            PVE::ReplicationState::delete_guest_states($vmid);\n\n            my $realcmd = sub {\n                my $conf = $param;\n                my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n\n                for my $opt (sort keys $param->%*) {\n                    next if $opt !~ m/^scsi\\d+$/;\n                    assert_scsi_feature_compatibility($opt, $conf, $storecfg, $param->{$opt});\n                }\n\n                $conf->{meta} = PVE::QemuServer::MetaInfo::new_meta_info_string();\n\n                my $vollist = [];\n                eval {\n                    ($vollist, my $created_opts, $live_import_mapping) = create_disks(\n                        $rpcenv,\n                        $authuser,\n                        $conf,\n                        $arch,\n                        $storecfg,\n                        $vmid,\n                        $pool,\n                        $param,\n                        $storage,\n                        $live_restore,\n                        $extraction_storage,\n                    );\n                    $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;\n\n                    if (!$conf->{boot}) {\n                        my $devs = PVE::QemuServer::get_default_bootdevices($conf);\n                        $conf->{boot} = PVE::QemuServer::print_bootorder($devs);\n                    }\n\n                    my $vga = PVE::QemuServer::parse_vga($conf->{vga});\n                    PVE::QemuServer::assert_clipboard_config($vga);\n\n                    # auto generate uuid if user did not specify smbios1 option\n                    if (!$conf->{smbios1}) {\n                        $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();\n                    }\n\n                    if (\n                        (!defined($conf->{vmgenid}) || $conf->{vmgenid} eq '1')\n                        && $arch ne 'aarch64'\n                    ) {\n                        $conf->{vmgenid} = PVE::QemuServer::generate_uuid();\n                    }\n\n                    # always pin Windows' machine version on create, they get confused too easily\n                    my $machine_string = PVE::QemuServer::Machine::check_and_pin_machine_string(\n                        $conf->{machine}, $conf->{ostype}, $arch,\n                    );\n                    $conf->{machine} = $machine_string if $machine_string;\n\n                    $conf->{lock} = 'import' if $live_import_mapping;\n\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                };\n                my $err = $@;\n\n                if ($err) {\n                    foreach my $volid (@$vollist) {\n                        eval { PVE::Storage::vdisk_free($storecfg, $volid); };\n                        warn $@ if $@;\n                    }\n                    die \"$emsg $err\";\n                }\n\n                PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;\n\n                PVE::QemuServer::Network::create_ifaces_ipams_ips($conf, $vmid);\n            };\n\n            PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);\n\n            if ($ha_managed) {\n                print \"Add as HA resource\\n\";\n                my $state = $start_after_create ? 'started' : 'stopped';\n                my $cmd = ['ha-manager', 'add', \"vm:$vmid\", '--state', $state];\n                eval { run_command($cmd) };\n                warn $@ if $@;\n            }\n\n            if ($start_after_create && !$live_restore) {\n                print \"Execute autostart\\n\";\n                eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) };\n                warn $@ if $@;\n                return;\n            } else {\n                return $live_import_mapping;\n            }\n        };\n\n        my ($code, $worker_name);\n        if ($is_restore) {\n            $worker_name = 'qmrestore';\n            $code = sub {\n                eval { $restorefn->() };\n                if (my $err = $@) {\n                    eval { PVE::QemuConfig->remove_lock($vmid, 'create') };\n                    warn $@ if $@;\n                    if ($restored_data) {\n                        warn\n                            \"error after data was restored, VM disks should be OK but config may \"\n                            . \"require adaptions. VM $vmid state is NOT cleaned up.\\n\";\n                    } else {\n                        warn \"error before or during data restore, some or all disks were not \"\n                            . \"completely restored. VM $vmid state is NOT cleaned up.\\n\";\n                    }\n                    die $err;\n                }\n            };\n        } else {\n            $worker_name = 'qmcreate';\n            $code = sub {\n                # If a live import was requested the create function returns\n                # the mapping for the startup.\n                my $live_import_mapping = eval { $createfn->() };\n                if (my $err = $@) {\n                    eval {\n                        my $conffile = PVE::QemuConfig->config_file($vmid);\n                        unlink($conffile) or die \"failed to remove config file: $!\\n\";\n                    };\n                    warn $@ if $@;\n                    die $err;\n                }\n\n                if ($live_import_mapping) {\n                    my $import_options = {\n                        bwlimit => $bwlimit,\n                        live => 1,\n                    };\n\n                    my $conf = PVE::QemuConfig->load_config($vmid);\n                    PVE::QemuServer::live_import_from_files(\n                        $live_import_mapping, $vmid, $conf, $import_options,\n                    );\n                }\n            };\n        }\n\n        return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vmdiridx',\n    path => '{vmid}',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Directory index\",\n    permissions => {\n        user => 'all',\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => \"object\",\n            properties => {\n                subdir => { type => 'string' },\n            },\n        },\n        links => [{ rel => 'child', href => \"{subdir}\" }],\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $res = [\n            { subdir => 'config' },\n            { subdir => 'cloudinit' },\n            { subdir => 'pending' },\n            { subdir => 'status' },\n            { subdir => 'unlink' },\n            { subdir => 'vncproxy' },\n            { subdir => 'termproxy' },\n            { subdir => 'migrate' },\n            { subdir => 'resize' },\n            { subdir => 'move' },\n            { subdir => 'rrd' },\n            { subdir => 'rrddata' },\n            { subdir => 'monitor' },\n            { subdir => 'agent' },\n            { subdir => 'snapshot' },\n            { subdir => 'spiceproxy' },\n            { subdir => 'sendkey' },\n            { subdir => 'firewall' },\n            { subdir => 'mtunnel' },\n            { subdir => 'remote_migrate' },\n        ];\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    subclass => \"PVE::API2::Firewall::VM\",\n    path => '{vmid}/firewall',\n});\n\n__PACKAGE__->register_method({\n    subclass => \"PVE::API2::Qemu::Agent\",\n    path => '{vmid}/agent',\n});\n\n__PACKAGE__->register_method({\n    name => 'rrd',\n    path => '{vmid}/rrd',\n    method => 'GET',\n    protected => 1, # fixme: can we avoid that?\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    description => \"Read VM RRD statistics (returns PNG)\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            timeframe => {\n                description => \"Specify the time frame you are interested in.\",\n                type => 'string',\n                enum => ['hour', 'day', 'week', 'month', 'year'],\n            },\n            ds => {\n                description => \"The list of datasources you want to display.\",\n                type => 'string',\n                format => 'pve-configid-list',\n            },\n            cf => {\n                description => \"The RRD consolidation function\",\n                type => 'string',\n                enum => ['AVERAGE', 'MAX'],\n                optional => 1,\n            },\n        },\n    },\n    returns => {\n        type => \"object\",\n        properties => {\n            filename => { type => 'string' },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        return PVE::RRD::create_rrd_graph(\n            \"pve-vm-9.0/$param->{vmid}\", $param->{timeframe}, $param->{ds}, $param->{cf},\n        );\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'rrddata',\n    path => '{vmid}/rrddata',\n    method => 'GET',\n    protected => 1, # fixme: can we avoid that?\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    description => \"Read VM RRD statistics\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            timeframe => {\n                description => \"Specify the time frame you are interested in.\",\n                type => 'string',\n                enum => ['hour', 'day', 'week', 'month', 'year'],\n            },\n            cf => {\n                description => \"The RRD consolidation function\",\n                type => 'string',\n                enum => ['AVERAGE', 'MAX'],\n                optional => 1,\n            },\n        },\n    },\n    returns => {\n        type => \"array\",\n        items => {\n            type => \"object\",\n            properties => {},\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        return PVE::RRD::create_rrd_data(\n            \"pve-vm-9.0/$param->{vmid}\", $param->{timeframe}, $param->{cf},\n        );\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_config',\n    path => '{vmid}/config',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Get the virtual machine configuration with pending configuration \"\n        . \"changes applied. Set the 'current' parameter to get the current configuration instead.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            current => {\n                description => \"Get current values (instead of pending values).\",\n                optional => 1,\n                default => 0,\n                type => 'boolean',\n            },\n            snapshot => get_standard_option(\n                'pve-snapshot-name',\n                {\n                    description => \"Fetch config values from given snapshot.\",\n                    optional => 1,\n                    completion => sub {\n                        my ($cmd, $pname, $cur, $args) = @_;\n                        PVE::QemuConfig->snapshot_list($args->[0]);\n                    },\n                },\n            ),\n        },\n    },\n    returns => {\n        description => \"The VM configuration.\",\n        type => \"object\",\n        properties => PVE::QemuServer::json_config_properties(\n            {\n                digest => {\n                    type => 'string',\n                    description =>\n                        'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',\n                },\n            },\n            0,\n            1,\n        ),\n    },\n    code => sub {\n        my ($param) = @_;\n\n        raise_param_exc({\n            snapshot => \"cannot use 'snapshot' parameter with 'current'\",\n            current => \"cannot use 'snapshot' parameter with 'current'\",\n        })\n            if ($param->{snapshot} && $param->{current});\n\n        my $conf;\n        if ($param->{snapshot}) {\n            $conf = PVE::QemuConfig->load_snapshot_config($param->{vmid}, $param->{snapshot});\n        } else {\n            $conf = PVE::QemuConfig->load_current_config($param->{vmid}, $param->{current});\n        }\n        $conf->{cipassword} = '**********' if $conf->{cipassword};\n        return $conf;\n\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_pending',\n    path => '{vmid}/pending',\n    method => 'GET',\n    proxyto => 'node',\n    description =>\n        \"Get the virtual machine configuration with both current and pending values.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n        },\n    },\n    returns => {\n        type => \"array\",\n        items => {\n            type => \"object\",\n            properties => {\n                key => {\n                    description => \"Configuration option name.\",\n                    type => 'string',\n                },\n                value => {\n                    description => \"Current value.\",\n                    type => 'string',\n                    optional => 1,\n                },\n                pending => {\n                    description => \"Pending value.\",\n                    type => 'string',\n                    optional => 1,\n                },\n                delete => {\n                    description => \"Indicates a pending delete request if present and not 0. \"\n                        . \"The value 2 indicates a force-delete request.\",\n                    type => 'integer',\n                    minimum => 0,\n                    maximum => 2,\n                    optional => 1,\n                },\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $conf = PVE::QemuConfig->load_config($param->{vmid});\n\n        my $pending_delete_hash =\n            PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});\n\n        $conf->{cipassword} = '**********' if defined($conf->{cipassword});\n        $conf->{pending}->{cipassword} = '********** '\n            if defined($conf->{pending}->{cipassword});\n\n        return PVE::GuestHelpers::config_with_pending_array($conf, $pending_delete_hash);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'cloudinit_pending',\n    path => '{vmid}/cloudinit',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Get the cloudinit configuration with both current and pending values.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n        },\n    },\n    returns => {\n        type => \"array\",\n        items => {\n            type => \"object\",\n            properties => {\n                key => {\n                    description => \"Configuration option name.\",\n                    type => 'string',\n                },\n                value => {\n                    description =>\n                        \"Value as it was used to generate the current cloudinit image.\",\n                    type => 'string',\n                    optional => 1,\n                },\n                pending => {\n                    description => \"The new pending value.\",\n                    type => 'string',\n                    optional => 1,\n                },\n                delete => {\n                    description => \"Indicates a pending delete request if present and not 0. \",\n                    type => 'integer',\n                    minimum => 0,\n                    maximum => 1,\n                    optional => 1,\n                },\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $ci = $conf->{'special-sections'}->{cloudinit};\n\n        $conf->{cipassword} = '**********' if exists $conf->{cipassword};\n        $ci->{cipassword} = '**********' if exists $ci->{cipassword};\n\n        my $res = [];\n\n        # All the values that got added\n        my $added = delete($ci->{added}) // '';\n        for my $key (PVE::Tools::split_list($added)) {\n            push @$res, { key => $key, pending => $conf->{$key} };\n        }\n\n        # All already existing values (+ their new value, if it exists)\n        for my $opt (keys %$cloudinitoptions) {\n            next if !$conf->{$opt};\n            next if $added =~ m/$opt/;\n            my $item = {\n                key => $opt,\n            };\n\n            if (my $pending = $ci->{$opt}) {\n                $item->{value} = $pending;\n                $item->{pending} = $conf->{$opt};\n            } else {\n                $item->{value} = $conf->{$opt};\n            }\n\n            push @$res, $item;\n        }\n\n        # Now, we'll find the deleted ones\n        for my $opt (keys %$ci) {\n            next if $conf->{$opt};\n            push @$res, { key => $opt, delete => 1 };\n        }\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'cloudinit_update',\n    path => '{vmid}/cloudinit',\n    method => 'PUT',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Regenerate and change cloudinit config drive.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = extract_param($param, 'vmid');\n\n        PVE::QemuConfig->lock_config(\n            $vmid,\n            sub {\n                my $conf = PVE::QemuConfig->load_config($vmid);\n                PVE::QemuConfig->check_lock($conf);\n\n                my $storecfg = PVE::Storage::config();\n                PVE::QemuServer::vmconfig_update_cloudinit_drive($storecfg, $conf, $vmid);\n            },\n        );\n        return;\n    },\n});\n\n# POST/PUT {vmid}/config implementation\n#\n# The original API used PUT (idempotent) an we assumed that all operations\n# are fast. But it turned out that almost any configuration change can\n# involve hot-plug actions, or disk alloc/free. Such actions can take long\n# time to complete and have side effects (not idempotent).\n#\n# The new implementation uses POST and forks a worker process. We added\n# a new option 'background_delay'. If specified we wait up to\n# 'background_delay' second for the worker task to complete. It returns null\n# if the task is finished within that time, else we return the UPID.\n\nmy $update_vm_api = sub {\n    my ($param, $sync) = @_;\n\n    my $rpcenv = PVE::RPCEnvironment::get();\n\n    my $authuser = $rpcenv->get_user();\n\n    my $node = extract_param($param, 'node');\n\n    my $vmid = extract_param($param, 'vmid');\n\n    my $digest = extract_param($param, 'digest');\n\n    my $background_delay = extract_param($param, 'background_delay');\n\n    my $skip_cloud_init = extract_param($param, 'skip_cloud_init');\n\n    my $extraction_storage = extract_param($param, 'import-working-storage');\n\n    my @paramarr = (); # used for log message\n    foreach my $key (sort keys %$param) {\n        my $value = $key eq 'cipassword' ? '<hidden>' : $param->{$key};\n        push @paramarr, \"-$key\", $value;\n    }\n\n    my $skiplock = extract_param($param, 'skiplock');\n    raise_param_exc({ skiplock => \"Only root may use this option.\" })\n        if $skiplock && $authuser ne 'root@pam';\n\n    my $delete_str = extract_param($param, 'delete');\n\n    my $revert_str = extract_param($param, 'revert');\n\n    my $force = extract_param($param, 'force');\n\n    if (defined(my $ssh_keys = $param->{sshkeys})) {\n        $ssh_keys = URI::Escape::uri_unescape($ssh_keys);\n        PVE::Tools::validate_ssh_public_keys($ssh_keys);\n    }\n\n    $param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})\n        if defined($param->{cpuunits}); # clamp value depending on cgroup version\n\n    die \"no options specified\\n\" if !$delete_str && !$revert_str && !scalar(keys %$param);\n\n    my $storecfg = PVE::Storage::config();\n\n    &$resolve_cdrom_alias($param);\n\n    # now try to verify all parameters\n\n    my $revert = {};\n    foreach my $opt (PVE::Tools::split_list($revert_str)) {\n        if (!PVE::QemuServer::option_exists($opt)) {\n            raise_param_exc({ revert => \"unknown option '$opt'\" });\n        }\n\n        raise_param_exc({\n            delete => \"you can't use '-$opt' and \" . \"-revert $opt' at the same time\" })\n            if defined($param->{$opt});\n\n        $revert->{$opt} = 1;\n    }\n\n    my @delete = ();\n    foreach my $opt (PVE::Tools::split_list($delete_str)) {\n        $opt = 'ide2' if $opt eq 'cdrom';\n\n        raise_param_exc({\n            delete => \"you can't use '-$opt' and \" . \"-delete $opt' at the same time\" })\n            if defined($param->{$opt});\n\n        raise_param_exc({\n            revert => \"you can't use '-delete $opt' and \" . \"-revert $opt' at the same time\" })\n            if $revert->{$opt};\n\n        if (!PVE::QemuServer::option_exists($opt)) {\n            raise_param_exc({ delete => \"unknown option '$opt'\" });\n        }\n\n        push @delete, $opt;\n    }\n\n    my $repl_conf = PVE::ReplicationConfig->new();\n    my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);\n    my $check_replication = sub {\n        my ($drive) = @_;\n        return if !$is_replicated;\n        my $volid = $drive->{file};\n        return if !$volid || !($drive->{replicate} // 1);\n        return if PVE::QemuServer::drive_is_cdrom($drive);\n\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n        die \"cannot add non-managed/pass-through volume to a replicated VM\\n\"\n            if !defined($storeid);\n\n        return if defined($volname) && $volname eq 'cloudinit';\n\n        my $format;\n        if ($volid =~ $PVE::QemuServer::Drive::NEW_DISK_RE) {\n            $storeid = $2;\n            $format =\n                $drive->{format} || PVE::Storage::storage_default_format($storecfg, $storeid);\n        } else {\n            $format = (PVE::Storage::parse_volname($storecfg, $volid))[6];\n        }\n        return if PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);\n        my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n        return if $scfg->{shared};\n        die \"cannot add non-replicatable volume to a replicated VM\\n\";\n    };\n\n    $check_drive_param->($param, $storecfg, $check_replication);\n\n    foreach my $opt (keys %$param) {\n        if ($opt =~ m/^net(\\d+)$/) {\n            # add macaddr\n            my $net = PVE::QemuServer::Network::parse_net($param->{$opt});\n            $param->{$opt} = PVE::QemuServer::Network::print_net($net);\n        } elsif ($opt eq 'vmgenid') {\n            if ($param->{$opt} eq '1') {\n                $param->{$opt} = PVE::QemuServer::generate_uuid();\n            }\n        } elsif ($opt eq 'hookscript') {\n            eval { PVE::GuestHelpers::check_hookscript($param->{$opt}, $storecfg); };\n            raise_param_exc({ $opt => $@ }) if $@;\n        }\n    }\n\n    &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);\n\n    &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);\n\n    &$check_storage_access(\n        $rpcenv, $authuser, $storecfg, $vmid, $param, undef, $extraction_storage,\n    );\n\n    PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $param);\n\n    my $updatefn = sub {\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        die \"checksum mismatch (file change by other user?)\\n\"\n            if $digest && $digest ne $conf->{digest};\n\n        &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);\n\n        # FIXME: 'suspended' lock should probabyl be a state or \"weak\" lock?!\n        if (scalar(@delete) && grep { $_ eq 'vmstate' } @delete) {\n            if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {\n                delete $conf->{lock}; # for check lock check, not written out\n                push @delete, 'lock'; # this is the real deal to write it out\n            }\n            push @delete, 'runningmachine' if $conf->{runningmachine};\n            push @delete, 'runningcpu' if $conf->{runningcpu};\n            push @delete, 'running-nets-host-mtu' if $conf->{'running-nets-host-mtu'};\n        }\n\n        PVE::QemuConfig->check_lock($conf) if !$skiplock;\n\n        foreach my $opt (keys %$revert) {\n            if (defined($conf->{$opt})) {\n                $param->{$opt} = $conf->{$opt};\n            } elsif (defined($conf->{pending}->{$opt})) {\n                push @delete, $opt;\n            }\n        }\n\n        if ($param->{memory} || defined($param->{balloon})) {\n\n            my $memory = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory};\n            my $maxmem = get_current_memory($memory);\n            my $balloon =\n                defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon}\n                || $conf->{balloon};\n\n            die \"balloon value too large (must be smaller than assigned memory)\\n\"\n                if $balloon && $balloon > $maxmem;\n        }\n\n        PVE::Cluster::log_msg('info', $authuser, \"update VM $vmid: \" . join(' ', @paramarr));\n\n        my $worker = sub {\n\n            print \"update VM $vmid: \" . join(' ', @paramarr) . \"\\n\";\n\n            # write updates to pending section\n\n            my $modified = {}; # record what $option we modify\n\n            my @bootorder;\n            if (my $boot = $conf->{boot}) {\n                my $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $boot);\n                @bootorder = PVE::Tools::split_list($bootcfg->{order})\n                    if $bootcfg && $bootcfg->{order};\n            }\n            my $bootorder_deleted = grep { $_ eq 'bootorder' } @delete;\n\n            my $check_drive_perms = sub {\n                my ($opt, $val) = @_;\n                my $drive = PVE::QemuServer::parse_drive($opt, $val, 1);\n                if (PVE::QemuServer::drive_is_cloudinit($drive)) {\n                    $rpcenv->check_vm_perm(\n                        $authuser,\n                        $vmid,\n                        undef,\n                        ['VM.Config.Cloudinit', 'VM.Config.CDROM'],\n                    );\n                } elsif (PVE::QemuServer::drive_is_cdrom($drive, 1)) { # CDROM\n                    $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);\n                } else {\n                    $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);\n\n                }\n            };\n\n            foreach my $opt (@delete) {\n                $modified->{$opt} = 1;\n                $conf = PVE::QemuConfig->load_config($vmid); # update/reload\n\n                # value of what we want to delete, independent if pending or not\n                my $val = $conf->{$opt} // $conf->{pending}->{$opt};\n                if (!defined($val)) {\n                    warn \"cannot delete '$opt' - not set in current configuration!\\n\";\n                    $modified->{$opt} = 0;\n                    next;\n                }\n                my $is_pending_val = defined($conf->{pending}->{$opt});\n                delete $conf->{pending}->{$opt};\n\n                # remove from bootorder if necessary\n                if (!$bootorder_deleted && @bootorder && grep { $_ eq $opt } @bootorder) {\n                    @bootorder = grep { $_ ne $opt } @bootorder;\n                    $conf->{pending}->{boot} = PVE::QemuServer::print_bootorder(\\@bootorder);\n                    $modified->{boot} = 1;\n                }\n\n                if ($opt =~ m/^unused/) {\n                    my $drive = PVE::QemuServer::parse_drive($opt, $val);\n                    PVE::QemuConfig->check_protection(\n                        $conf,\n                        \"can't remove unused disk '$drive->{file}'\",\n                    );\n                    $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);\n                    if (PVE::QemuServer::try_deallocate_drive(\n                        $storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser,\n                    )) {\n                        delete $conf->{$opt};\n                        PVE::QemuConfig->write_config($vmid, $conf);\n                    }\n                } elsif ($opt eq 'vmstate') {\n                    PVE::QemuConfig->check_protection($conf, \"can't remove vmstate '$val'\");\n                    if (PVE::QemuServer::try_deallocate_drive(\n                        $storecfg,\n                        $vmid,\n                        $conf,\n                        $opt,\n                        { file => $val },\n                        $rpcenv,\n                        $authuser,\n                        1,\n                    )) {\n                        delete $conf->{$opt};\n                        PVE::QemuConfig->write_config($vmid, $conf);\n                    }\n                } elsif (PVE::QemuServer::is_valid_drivename($opt)) {\n                    PVE::QemuConfig->check_protection($conf, \"can't remove drive '$opt'\");\n                    $check_drive_perms->($opt, $val);\n                    PVE::QemuServer::vmconfig_register_unused_drive(\n                        $storecfg,\n                        $vmid,\n                        $conf,\n                        PVE::QemuServer::parse_drive($opt, $val),\n                    ) if $is_pending_val;\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt =~ m/^serial\\d+$/) {\n                    if ($val eq 'socket') {\n                        $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);\n                    } elsif ($authuser ne 'root@pam') {\n                        die \"only root can delete '$opt' config for real devices\\n\";\n                    }\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt =~ m/^usb\\d+$/) {\n                    check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $val);\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt =~ m/^hostpci\\d+$/) {\n                    check_hostpci_perm($rpcenv, $authuser, $vmid, undef, $opt, $val);\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt =~ m/^rng\\d+$/) {\n                    check_rng_perm($rpcenv, $authuser, $vmid, undef, $opt, $val);\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt =~ m/^virtiofs\\d$/) {\n                    check_dir_perm($rpcenv, $authuser, $vmid, undef, $opt, $val);\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt eq 'tags') {\n                    assert_tag_permissions($vmid, $val, '', $rpcenv, $authuser);\n                    delete $conf->{$opt};\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } elsif ($opt =~ m/^net\\d+$/) {\n                    if ($conf->{$opt}) {\n                        PVE::QemuServer::check_bridge_access(\n                            $rpcenv,\n                            $authuser,\n                            { $opt => $conf->{$opt} },\n                        );\n                    }\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                } else {\n                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);\n                    PVE::QemuConfig->write_config($vmid, $conf);\n                }\n            }\n\n            foreach my $opt (keys %$param) { # add/change\n                $modified->{$opt} = 1;\n                $conf = PVE::QemuConfig->load_config($vmid); # update/reload\n                next\n                    if defined($conf->{pending}->{$opt})\n                    && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed\n\n                my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n\n                if (PVE::QemuServer::is_valid_drivename($opt)) {\n                    # old drive\n                    if ($conf->{$opt}) {\n                        $check_drive_perms->($opt, $conf->{$opt});\n                        prohibit_tpm_version_change($conf->{$opt}, $param->{$opt})\n                            if $opt eq 'tpmstate0';\n                    }\n\n                    # new drive\n                    $check_drive_perms->($opt, $param->{$opt});\n                    PVE::QemuServer::vmconfig_register_unused_drive(\n                        $storecfg,\n                        $vmid,\n                        $conf,\n                        PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}),\n                    ) if defined($conf->{pending}->{$opt});\n\n                    assert_scsi_feature_compatibility($opt, $conf, $storecfg, $param->{$opt})\n                        if $opt =~ m/^scsi\\d+$/;\n\n                    my (undef, $created_opts) = create_disks(\n                        $rpcenv,\n                        $authuser,\n                        $conf,\n                        $arch,\n                        $storecfg,\n                        $vmid,\n                        undef,\n                        { $opt => $param->{$opt} },\n                        undef,\n                        undef,\n                        $extraction_storage,\n                    );\n                    $conf->{pending}->{$_} = $created_opts->{$_} for keys $created_opts->%*;\n\n                    # default legacy boot order implies all cdroms anyway\n                    if (@bootorder) {\n                        # append new CD drives to bootorder to mark them bootable\n                        my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}, 1);\n                        if (\n                            PVE::QemuServer::drive_is_cdrom($drive, 1)\n                            && !grep(/^$opt$/, @bootorder)\n                        ) {\n                            push @bootorder, $opt;\n                            $conf->{pending}->{boot} =\n                                PVE::QemuServer::print_bootorder(\\@bootorder);\n                            $modified->{boot} = 1;\n                        }\n                    }\n                } elsif ($opt =~ m/^serial\\d+/) {\n                    if (\n                        (!defined($conf->{$opt}) || $conf->{$opt} eq 'socket')\n                        && $param->{$opt} eq 'socket'\n                    ) {\n                        $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);\n                    } elsif ($authuser ne 'root@pam') {\n                        die \"only root can modify '$opt' config for real devices\\n\";\n                    }\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt eq 'vga') {\n                    my $vga = PVE::QemuServer::parse_vga($param->{$opt});\n                    PVE::QemuServer::assert_clipboard_config($vga);\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt =~ m/^usb\\d+/) {\n                    if (my $olddevice = $conf->{$opt}) {\n                        check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});\n                    }\n                    check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt =~ m/^hostpci\\d+$/) {\n                    if (my $oldvalue = $conf->{$opt}) {\n                        check_hostpci_perm($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);\n                    }\n                    check_hostpci_perm($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt =~ m/^rng\\d+$/) {\n                    if (my $oldvalue = $conf->{$opt}) {\n                        check_rng_perm($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);\n                    }\n                    check_rng_perm($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt =~ m/^virtiofs\\d$/) {\n                    if (my $oldvalue = $conf->{$opt}) {\n                        check_dir_perm($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);\n                    }\n                    check_dir_perm($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt eq 'tags') {\n                    assert_tag_permissions(\n                        $vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser,\n                    );\n                    $conf->{pending}->{$opt} =\n                        PVE::GuestHelpers::get_unique_tags($param->{$opt});\n                } elsif ($opt =~ m/^net\\d+$/) {\n                    if ($conf->{$opt}) {\n                        PVE::QemuServer::check_bridge_access(\n                            $rpcenv,\n                            $authuser,\n                            { $opt => $conf->{$opt} },\n                        );\n                    }\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt eq 'machine') {\n                    my $machine_conf = PVE::QemuServer::Machine::parse_machine($param->{$opt});\n                    PVE::QemuServer::Machine::assert_valid_machine_property($machine_conf);\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt eq 'ostype') {\n                    # Check if machine version pinning is needed when switching OS type, just like\n                    # upon creation. Skip if 'machine' is explicitly set or removed at the same time\n                    # to honor the users request. While it should be enough to look at $modified,\n                    # because 'machine' is sorted before 'ostype', be explicit just to be sure.\n                    if (\n                        !defined($param->{machine})\n                        && !defined($conf->{pending}->{machine})\n                        && !$modified->{machine} # detects deletion\n                    ) {\n                        eval {\n                            my $machine_string =\n                                PVE::QemuServer::Machine::check_and_pin_machine_string(\n                                    $conf->{machine}, $param->{ostype}, $arch,\n                                );\n                            $conf->{pending}->{machine} = $machine_string if $machine_string;\n                        };\n                        print \"automatic pinning of machine version failed - $@\" if $@;\n                    }\n                    $conf->{pending}->{$opt} = $param->{$opt};\n                } elsif ($opt eq 'cipassword') {\n                    if (!PVE::QemuServer::Helpers::windows_version($conf->{ostype})) {\n                        # Same logic as in cloud-init (but with the regex fixed...)\n                        $param->{cipassword} = PVE::Tools::encrypt_pw($param->{cipassword})\n                            if $param->{cipassword} !~ /^\\$(?:[156]|2[ay])(\\$.+){2}/;\n                    }\n                    $conf->{cipassword} = $param->{cipassword};\n                } else {\n                    $conf->{pending}->{$opt} = $param->{$opt};\n\n                    if ($opt eq 'boot') {\n                        my $new_bootcfg =\n                            PVE::JSONSchema::parse_property_string('pve-qm-boot', $param->{$opt});\n                        if ($new_bootcfg->{order}) {\n                            my @devs = PVE::Tools::split_list($new_bootcfg->{order});\n                            for my $dev (@devs) {\n                                my $exists =\n                                    $conf->{$dev} || $conf->{pending}->{$dev} || $param->{$dev};\n                                my $deleted = grep { $_ eq $dev } @delete;\n                                die \"invalid bootorder: device '$dev' does not exist'\\n\"\n                                    if !$exists || $deleted;\n                            }\n\n                            # remove legacy boot order settings if new one set\n                            $conf->{pending}->{$opt} = PVE::QemuServer::print_bootorder(\\@devs);\n                            PVE::QemuConfig->add_to_pending_delete($conf, \"bootdisk\")\n                                if $conf->{bootdisk};\n                        }\n                    }\n                }\n                PVE::QemuConfig->remove_from_pending_delete($conf, $opt);\n                PVE::QemuConfig->write_config($vmid, $conf);\n            }\n\n            # remove pending changes when nothing changed\n            $conf = PVE::QemuConfig->load_config($vmid); # update/reload\n            my $changes = PVE::QemuConfig->cleanup_pending($conf);\n            PVE::QemuConfig->write_config($vmid, $conf) if $changes;\n\n            return if !scalar(keys %{ $conf->{pending} });\n\n            my $running = PVE::QemuServer::check_running($vmid);\n\n            # apply pending changes\n\n            $conf = PVE::QemuConfig->load_config($vmid); # update/reload\n\n            my $errors = {};\n            if ($running) {\n                PVE::QemuServer::vmconfig_hotplug_pending(\n                    $vmid, $conf, $storecfg, $modified, $errors,\n                );\n            } else {\n                # cloud_init must be skipped if we are in an incoming, remote live migration\n                PVE::QemuServer::vmconfig_apply_pending(\n                    $vmid, $conf, $storecfg, $errors, $skip_cloud_init,\n                );\n            }\n            raise_param_exc($errors) if scalar(keys %$errors);\n\n            return;\n        };\n\n        if ($sync) {\n            &$worker();\n            return;\n        } else {\n            my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);\n\n            if ($background_delay) {\n\n                # Note: It would be better to do that in the Event based HTTPServer\n                # to avoid blocking call to sleep.\n\n                my $end_time = time() + $background_delay;\n\n                my $task = PVE::Tools::upid_decode($upid);\n\n                my $running = 1;\n                while (time() < $end_time) {\n                    $running =\n                        PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});\n                    last if !$running;\n                    sleep(1); # this gets interrupted when child process ends\n                }\n\n                if (!$running) {\n                    my $status = PVE::Tools::upid_read_status($upid);\n                    return if !PVE::Tools::upid_status_is_error($status);\n                    die \"failed to update VM $vmid: $status\\n\";\n                }\n            }\n\n            return $upid;\n        }\n    };\n\n    return PVE::QemuConfig->lock_config($vmid, $updatefn);\n};\n\nmy $vm_config_perm_list = [\n    'VM.Config.Disk',\n    'VM.Config.CDROM',\n    'VM.Config.CPU',\n    'VM.Config.Memory',\n    'VM.Config.Network',\n    'VM.Config.HWType',\n    'VM.Config.Options',\n    'VM.Config.Cloudinit',\n];\n\n__PACKAGE__->register_method({\n    name => 'update_vm_async',\n    path => '{vmid}/config',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Set virtual machine options (asynchronous API).\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => PVE::QemuServer::json_config_properties(\n            {\n                node => get_standard_option('pve-node'),\n                vmid => get_standard_option('pve-vmid'),\n                skiplock => get_standard_option('skiplock'),\n                delete => {\n                    type => 'string',\n                    format => 'pve-configid-list',\n                    description => \"A list of settings you want to delete.\",\n                    optional => 1,\n                },\n                revert => {\n                    type => 'string',\n                    format => 'pve-configid-list',\n                    description => \"Revert a pending change.\",\n                    optional => 1,\n                },\n                force => {\n                    type => 'boolean',\n                    description => $opt_force_description,\n                    optional => 1,\n                    requires => 'delete',\n                },\n                digest => {\n                    type => 'string',\n                    description =>\n                        'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',\n                    maxLength => 40,\n                    optional => 1,\n                },\n                background_delay => {\n                    type => 'integer',\n                    description =>\n                        \"Time to wait for the task to finish. We return 'null' if the task finish within that time.\",\n                    minimum => 1,\n                    maximum => 30,\n                    optional => 1,\n                },\n                'import-working-storage' => get_standard_option(\n                    'pve-storage-id',\n                    {\n                        description =>\n                            \"A file-based storage with 'images' content-type enabled, which\"\n                            . \" is used as an intermediary extraction storage during import. Defaults to\"\n                            . \" the source storage.\",\n                        optional => 1,\n                        completion => \\&PVE::QemuServer::complete_storage,\n                    },\n                ),\n            },\n            1, # with_disk_alloc\n        ),\n    },\n    returns => {\n        type => 'string',\n        optional => 1,\n    },\n    code => $update_vm_api,\n});\n\n__PACKAGE__->register_method({\n    name => 'update_vm',\n    path => '{vmid}/config',\n    method => 'PUT',\n    protected => 1,\n    proxyto => 'node',\n    description =>\n        \"Set virtual machine options (synchronous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => PVE::QemuServer::json_config_properties(\n            {\n                node => get_standard_option('pve-node'),\n                vmid => get_standard_option(\n                    'pve-vmid',\n                    { completion => \\&PVE::QemuServer::complete_vmid },\n                ),\n                skiplock => get_standard_option('skiplock'),\n                delete => {\n                    type => 'string',\n                    format => 'pve-configid-list',\n                    description => \"A list of settings you want to delete.\",\n                    optional => 1,\n                },\n                revert => {\n                    type => 'string',\n                    format => 'pve-configid-list',\n                    description => \"Revert a pending change.\",\n                    optional => 1,\n                },\n                force => {\n                    type => 'boolean',\n                    description => $opt_force_description,\n                    optional => 1,\n                    requires => 'delete',\n                },\n                digest => {\n                    type => 'string',\n                    description =>\n                        'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',\n                    maxLength => 40,\n                    optional => 1,\n                },\n            },\n            1, # with_disk_alloc\n        ),\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n        &$update_vm_api($param, 1);\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'destroy_vm',\n    path => '{vmid}',\n    method => 'DELETE',\n    protected => 1,\n    proxyto => 'node',\n    description =>\n        \"Destroy the VM and  all used/owned volumes. Removes any VM specific permissions\"\n        . \" and firewall rules\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Allocate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_stopped },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            purge => {\n                type => 'boolean',\n                description =>\n                    \"Remove VMID from configurations, like backup & replication jobs and HA.\",\n                optional => 1,\n            },\n            'destroy-unreferenced-disks' => {\n                type => 'boolean',\n                description =>\n                    \"If set, destroy additionally all disks not referenced in the config\"\n                    . \" but with a matching VMID from all enabled storages.\",\n                optional => 1,\n                default => 0,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n        my $vmid = $param->{vmid};\n\n        my $skiplock = $param->{skiplock};\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        my $early_checks = sub {\n            # test if VM exists\n            my $conf = PVE::QemuConfig->load_config($vmid);\n            PVE::QemuConfig->check_protection($conf, \"can't remove VM $vmid\");\n\n            my $ha_managed = PVE::HA::Config::service_is_configured(\"vm:$vmid\");\n\n            if (!$param->{purge}) {\n                die\n                    \"unable to remove VM $vmid - used in HA resources and purge parameter not set.\\n\"\n                    if $ha_managed;\n                # don't allow destroy if with replication jobs but no purge param\n                my $repl_conf = PVE::ReplicationConfig->new();\n                $repl_conf->check_for_existing_jobs($vmid);\n            }\n\n            die \"VM $vmid is running - destroy failed\\n\"\n                if PVE::QemuServer::check_running($vmid);\n\n            return $ha_managed;\n        };\n\n        $early_checks->();\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            my $storecfg = PVE::Storage::config();\n\n            syslog('info', \"destroy VM $vmid: $upid\\n\");\n            PVE::QemuConfig->lock_config(\n                $vmid,\n                sub {\n                    # repeat, config might have changed\n                    my $ha_managed = $early_checks->();\n\n                    my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};\n\n                    PVE::QemuServer::destroy_vm(\n                        $storecfg,\n                        $vmid,\n                        $skiplock,\n                        { lock => 'destroyed' },\n                        $purge_unreferenced,\n                    );\n\n                    PVE::AccessControl::remove_vm_access($vmid);\n                    PVE::Firewall::remove_vmfw_conf($vmid);\n                    if ($param->{purge}) {\n                        print \"purging VM $vmid from related configurations..\\n\";\n                        PVE::ReplicationConfig::remove_vmid_jobs($vmid);\n                        PVE::VZDump::Plugin::remove_vmid_from_backup_jobs($vmid);\n\n                        if ($ha_managed) {\n                            PVE::HA::Config::delete_service_from_config(\n                                \"vm:$vmid\", $param->{purge},\n                            );\n                            print \"NOTE: removed VM $vmid from HA resource configuration.\\n\";\n                        }\n                    }\n\n                    # only now remove the zombie config, else we can have reuse race\n                    PVE::QemuConfig->destroy_config($vmid);\n                },\n            );\n        };\n\n        return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'unlink',\n    path => '{vmid}/unlink',\n    method => 'PUT',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Unlink/delete disk images.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Config.Disk']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            idlist => {\n                type => 'string',\n                format => 'pve-configid-list',\n                description => \"A list of disk IDs you want to delete.\",\n            },\n            force => {\n                type => 'boolean',\n                description => $opt_force_description,\n                optional => 1,\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        $param->{delete} = extract_param($param, 'idlist');\n\n        __PACKAGE__->update_vm($param);\n\n        return;\n    },\n});\n\nmy $sslcert;\n\n__PACKAGE__->register_method({\n    name => 'vncproxy',\n    path => '{vmid}/vncproxy',\n    method => 'POST',\n    protected => 1,\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Console']],\n    },\n    description => \"Creates a TCP VNC proxy connections.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            websocket => {\n                optional => 1,\n                type => 'boolean',\n                description => \"Prepare for websocket upgrade (only required when using \"\n                    . \"serial terminal, otherwise upgrade is always possible).\",\n            },\n            # FIXME: MAJOR VERSION: Drop this, require always using explicit 'password' return value\n            'generate-password' => {\n                optional => 1,\n                type => 'boolean',\n                default => 0,\n                description => \"Deprecated, do not use. Password is generated when required.\",\n            },\n        },\n    },\n    returns => {\n        additionalProperties => 0,\n        properties => {\n            user => { type => 'string' },\n            ticket => { type => 'string' },\n            password => {\n                optional => 1,\n                description => \"Password used for authentication within the VNC protocol.\"\n                    . \" Consists of printable ASCII characters ('!' .. '~').\",\n                type => 'string',\n            },\n            cert => { type => 'string' },\n            port => { type => 'integer' },\n            upid => { type => 'string' },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = $param->{vmid};\n        my $node = $param->{node};\n        my $websocket = $param->{websocket};\n\n        my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists\n\n        my $serial;\n        if ($conf->{vga}) {\n            my $vga = PVE::QemuServer::parse_vga($conf->{vga});\n            $serial = $vga->{type} if defined($vga->{type}) && $vga->{type} =~ m/^serial\\d+$/;\n        }\n\n        my $authpath = \"/vms/$vmid\";\n\n        $sslcert = PVE::Tools::file_get_contents(\"/etc/pve/pve-root-ca.pem\", 8192)\n            if !$sslcert;\n\n        my $family;\n        my $remcmd = [];\n\n        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {\n            (undef, $family) = PVE::Cluster::remote_node_ip($node);\n            my $sshinfo = PVE::SSHInfo::get_ssh_info($node);\n            # NOTE: kvm VNC traffic is already TLS encrypted or is known insecure\n            $remcmd =\n                PVE::SSHInfo::ssh_info_to_command($sshinfo, defined($serial) ? '-t' : '-T');\n        } else {\n            $family = PVE::Tools::get_host_address_family($node);\n        }\n\n        my $port = PVE::Tools::next_vnc_port($family);\n\n        my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath, $port);\n        my $password;\n        if ($param->{'generate-password'} || !defined($serial) || $param->{websocket}) {\n            $password = PVE::Ticket::generate_vnc_password();\n            # FIXME: MAJOR VERSION: Avoid this hack, require using explicit 'password' return value\n            $ticket = \"${password}:${ticket}\";\n        } # else authentication happens via ticket only, not via password in VNC protocol\n\n        my $timeout = 10;\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            syslog('info', \"starting vnc proxy $upid\\n\");\n\n            my $cmd;\n\n            if (defined($serial)) {\n\n                my $termcmd =\n                    ['/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0'];\n\n                $cmd = [\n                    '/usr/bin/vncterm',\n                    '-rfbport',\n                    $port,\n                    '-timeout',\n                    $timeout,\n                    '-authpath',\n                    $authpath,\n                    '-perm',\n                    'Sys.Console',\n                    '-verify-port',\n                ];\n\n                if ($param->{websocket}) {\n                    $ENV{PVE_VNC_TICKET} = $password; # pass VNC protocol password to vncterm\n                    push @$cmd, '-notls', '-listen', 'localhost';\n                } else {\n                    $ENV{PVE_VNC_TICKET} = $ticket; # pass VNC ticket to vncterm\n                }\n\n                push @$cmd, '-c', @$remcmd, @$termcmd;\n\n                run_command($cmd);\n\n            } else {\n                $ENV{LC_PVE_TICKET} = $password; # set VNC protocol password with \"qm vncproxy\"\n\n                $cmd = [@$remcmd, \"/usr/sbin/qm\", 'vncproxy', $vmid];\n\n                my $sock;\n\n                if ($param->{websocket}) {\n                    # listen for localhost only, no TLS\n                    my $socket_params = {\n                        ReuseAddr => 1,\n                        Listen => 1,\n                        LocalPort => $port,\n                        Proto => 'tcp',\n                        GetAddrInfoFlags => 0,\n                        LocalHost => 'localhost',\n                    };\n\n                    $sock = IO::Socket::IP->new($socket_params->%*)\n                        or die \"failed to create socket: $!\\n\";\n                } else {\n                    my $socket_params = {\n                        ReuseAddr => 1,\n                        Listen => 1,\n                        LocalPort => $port,\n                        Proto => 'tcp',\n                        GetAddrInfoFlags => 0,\n                        SSL_server => 1,\n                    };\n\n                    my $pveproxy_cert_file = '/etc/pve/local/pveproxy-ssl.pem';\n                    my $pveproxy_key_file = '/etc/pve/local/pveproxy-ssl.key';\n                    if (-f $pveproxy_cert_file && -f $pveproxy_key_file) {\n                        $socket_params->{SSL_cert_file} = $pveproxy_cert_file;\n                        $socket_params->{SSL_key_file} = $pveproxy_key_file;\n                    } else {\n                        $socket_params->{SSL_cert_file} = '/etc/pve/local/pve-ssl.pem';\n                        $socket_params->{SSL_key_file} = '/etc/pve/local/pve-ssl.key';\n                    }\n\n                    $sock = IO::Socket::SSL->new($socket_params->%*)\n                        or die \"failed to create SSL socket: $!\\n\";\n                }\n\n                # Inside the worker we shouldn't have any previous alarms\n                # running anyway...:\n                alarm(0);\n                local $SIG{ALRM} = sub { die \"connection timed out\\n\" };\n                alarm $timeout;\n                accept(my $cli, $sock) or die \"connection failed: $!\\n\";\n                alarm(0);\n                close($sock);\n                if (\n                    run_command(\n                        $cmd,\n                        output => '>&' . fileno($cli),\n                        input => '<&' . fileno($cli),\n                        noerr => 1,\n                    ) != 0\n                ) {\n                    die \"Failed to run vncproxy.\\n\";\n                }\n            }\n\n            return;\n        };\n\n        my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);\n\n        PVE::Tools::wait_for_vnc_port($port);\n\n        my $res = {\n            user => $authuser,\n            ticket => $ticket,\n            port => $port,\n            upid => $upid,\n            cert => $sslcert,\n        };\n\n        $res->{password} = $password if defined($password);\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'termproxy',\n    path => '{vmid}/termproxy',\n    method => 'POST',\n    protected => 1,\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Console']],\n    },\n    description => \"Creates a TCP proxy connections.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            serial => {\n                optional => 1,\n                type => 'string',\n                enum => [qw(serial0 serial1 serial2 serial3)],\n                description => \"opens a serial terminal (defaults to display)\",\n            },\n        },\n    },\n    returns => {\n        additionalProperties => 0,\n        properties => {\n            user => { type => 'string' },\n            ticket => { type => 'string' },\n            port => { type => 'integer' },\n            upid => { type => 'string' },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = $param->{vmid};\n        my $node = $param->{node};\n        my $serial = $param->{serial};\n\n        my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists\n\n        if (!defined($serial)) {\n            if ($conf->{vga}) {\n                my $vga = PVE::QemuServer::parse_vga($conf->{vga});\n                $serial = $vga->{type}\n                    if defined($vga->{type}) && $vga->{type} =~ m/^serial\\d+$/;\n            }\n        }\n\n        my $authpath = \"/vms/$vmid\";\n\n        my $family;\n        my $remcmd = [];\n\n        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {\n            (undef, $family) = PVE::Cluster::remote_node_ip($node);\n            my $sshinfo = PVE::SSHInfo::get_ssh_info($node);\n            $remcmd = PVE::SSHInfo::ssh_info_to_command($sshinfo, '-t');\n            push @$remcmd, '--';\n        } else {\n            $family = PVE::Tools::get_host_address_family($node);\n        }\n\n        my $port = PVE::Tools::next_vnc_port($family);\n        my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath, $port);\n\n        my $termcmd = ['/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];\n        push @$termcmd, '-iface', $serial if $serial;\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            syslog('info', \"starting qemu termproxy $upid\\n\");\n\n            pipe(my $ticket_rd, my $ticket_wr) or die \"failed to create pipe: $!\\n\";\n\n            my $flags = fcntl($ticket_rd, F_GETFD, 0)\n                // die \"failed to get file descriptor flags: $!\\n\";\n            fcntl($ticket_rd, F_SETFD, $flags & ~FD_CLOEXEC)\n                // die \"failed to remove CLOEXEC flag from fd: $!\\n\";\n\n            my $cmd = [\n                '/usr/bin/termproxy',\n                $port,\n                '--path',\n                $authpath,\n                '--perm',\n                'VM.Console',\n                '--vncticket-endpoint',\n                '--verify-port',\n                '--ticket-fd',\n                fileno($ticket_rd),\n                '--',\n            ];\n            push @$cmd, @$remcmd, @$termcmd;\n\n            my $afterfork = sub {\n                print {$ticket_wr} $ticket;\n                close($ticket_wr);\n            };\n\n            run_command($cmd, afterfork => $afterfork);\n        };\n\n        my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);\n\n        PVE::Tools::wait_for_vnc_port($port);\n\n        return {\n            user => $authuser,\n            ticket => $ticket,\n            port => $port,\n            upid => $upid,\n        };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vncwebsocket',\n    path => '{vmid}/vncwebsocket',\n    method => 'GET',\n    permissions => {\n        description => \"You also need to pass a valid ticket (vncticket).\",\n        check => ['perm', '/vms/{vmid}', ['VM.Console']],\n    },\n    description => \"Opens a websocket for VNC traffic.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            vncticket => {\n                description => \"Ticket from previous call to vncproxy.\",\n                type => 'string',\n                maxLength => 512,\n            },\n            port => {\n                description => \"Port number returned by previous vncproxy call.\",\n                type => 'integer',\n                minimum => 5900,\n                maximum => 5999,\n            },\n        },\n    },\n    returns => {\n        type => \"object\",\n        properties => {\n            port => { type => 'string' },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = $param->{vmid};\n        my $node = $param->{node};\n\n        my $authpath = \"/vms/$vmid\";\n\n        # Note: VNC ports may be accessible from outside, so there is a password that needs to\n        # additionally be checked inside the VNC protocol.\n\n        my $port = $param->{port};\n\n        PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath, $port);\n\n        my $conf = PVE::QemuConfig->load_config($vmid, $node); # VM exists ?\n\n        return { port => $port };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'spiceproxy',\n    path => '{vmid}/spiceproxy',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Console']],\n    },\n    description => \"Returns a SPICE configuration to connect to the VM.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            proxy => get_standard_option('spice-proxy', { optional => 1 }),\n        },\n    },\n    returns => get_standard_option('remote-viewer-config'),\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = $param->{vmid};\n        my $node = $param->{node};\n        my $proxy = $param->{proxy};\n\n        my $conf = PVE::QemuConfig->load_config($vmid, $node);\n        my $title = \"VM $vmid\";\n        $title .= \" - \" . $conf->{name} if $conf->{name};\n\n        my $port = PVE::QemuServer::spice_port($vmid);\n\n        my ($ticket, undef, $remote_viewer_config) =\n            PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title,\n                $port);\n\n        mon_cmd($vmid, \"set_password\", protocol => 'spice', password => $ticket);\n        mon_cmd($vmid, \"expire_password\", protocol => 'spice', time => \"+30\");\n\n        return $remote_viewer_config;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vmcmdidx',\n    path => '{vmid}/status',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Directory index\",\n    permissions => {\n        user => 'all',\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => \"object\",\n            properties => {\n                subdir => { type => 'string' },\n            },\n        },\n        links => [{ rel => 'child', href => \"{subdir}\" }],\n    },\n    code => sub {\n        my ($param) = @_;\n\n        # test if VM exists\n        my $conf = PVE::QemuConfig->load_config($param->{vmid});\n\n        my $res = [\n            { subdir => 'current' },\n            { subdir => 'start' },\n            { subdir => 'stop' },\n            { subdir => 'reset' },\n            { subdir => 'shutdown' },\n            { subdir => 'suspend' },\n            { subdir => 'reboot' },\n        ];\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_status',\n    path => '{vmid}/status/current',\n    method => 'GET',\n    proxyto => 'node',\n    protected => 1, # qemu pid files are only readable by root\n    description => \"Get virtual machine status.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n        },\n    },\n    returns => {\n        type => 'object',\n        properties => {\n            %$PVE::QemuServer::vmstatus_return_properties,\n            ha => {\n                description => \"HA manager service status.\",\n                type => 'object',\n            },\n            spice => {\n                description => \"QEMU VGA configuration supports spice.\",\n                type => 'boolean',\n                optional => 1,\n            },\n            agent => {\n                description => \"QEMU Guest Agent is enabled in config.\",\n                type => 'boolean',\n                optional => 1,\n            },\n            clipboard => {\n                description => 'Enable a specific clipboard. If not set, depending on'\n                    . ' the display type the SPICE one will be added.',\n                type => 'string',\n                enum => ['vnc'],\n                optional => 1,\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        # test if VM exists\n        my $conf = PVE::QemuConfig->load_config($param->{vmid});\n\n        my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);\n        my $status = $vmstatus->{ $param->{vmid} };\n\n        $status->{ha} = PVE::HA::Config::get_service_status(\"vm:$param->{vmid}\");\n\n        if ($conf->{vga}) {\n            my $vga = PVE::QemuServer::parse_vga($conf->{vga});\n            my $spice = defined($vga->{type}) && $vga->{type} =~ /^virtio/;\n            $spice ||= PVE::QemuServer::vga_conf_has_spice($conf->{vga});\n            $status->{spice} = 1 if $spice;\n            $status->{clipboard} = $vga->{clipboard};\n        }\n        $status->{agent} = 1 if PVE::QemuServer::Agent::get_qga_key($conf, 'enabled');\n\n        return $status;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_start',\n    path => '{vmid}/status/start',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Start virtual machine.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_stopped },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            stateuri => get_standard_option('pve-qm-stateuri'),\n            migratedfrom => get_standard_option('pve-node', { optional => 1 }),\n            migration_type => {\n                type => 'string',\n                enum => ['secure', 'insecure'],\n                description => \"Migration traffic is encrypted using an SSH \"\n                    . \"tunnel by default. On secure, completely private networks \"\n                    . \"this can be disabled to increase performance.\",\n                optional => 1,\n            },\n            migration_network => {\n                type => 'string',\n                format => 'CIDR',\n                description => \"CIDR of the (sub) network that is used for migration.\",\n                optional => 1,\n            },\n            machine => get_standard_option('pve-qemu-machine'),\n            'force-cpu' => {\n                description => \"Override QEMU's -cpu argument with the given string.\",\n                type => 'string',\n                optional => 1,\n            },\n            targetstorage => get_standard_option('pve-targetstorage'),\n            timeout => {\n                description => \"Wait maximal timeout seconds.\",\n                type => 'integer',\n                minimum => 0,\n                default => 'max(30, vm memory in GiB)',\n                optional => 1,\n            },\n            'with-conntrack-state' => {\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n                description => 'Whether to migrate conntrack entries for running VMs.',\n            },\n            'nets-host-mtu' => {\n                type => 'string',\n                pattern => 'net\\d+=\\d+(,net\\d+=\\d+)*',\n                optional => 1,\n                description =>\n                    'Used for migration compat. List of VirtIO network devices and their effective'\n                    . ' host_mtu setting according to the QEMU object model on the source side of'\n                    . ' the migration. A value of 0 means that the host_mtu parameter is to be'\n                    . ' avoided for the corresponding device.',\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n        my $timeout = extract_param($param, 'timeout');\n        my $machine = extract_param($param, 'machine');\n\n        my $get_root_param = sub {\n            my $value = extract_param($param, $_[0]);\n            raise_param_exc({ \"$_[0]\" => \"Only root may use this option.\" })\n                if $value && $authuser ne 'root@pam';\n            return $value;\n        };\n\n        my $stateuri = $get_root_param->('stateuri');\n        my $skiplock = $get_root_param->('skiplock');\n        my $migratedfrom = $get_root_param->('migratedfrom');\n        my $migration_type = $get_root_param->('migration_type');\n        my $migration_network = $get_root_param->('migration_network');\n        my $targetstorage = $get_root_param->('targetstorage');\n        my $force_cpu = $get_root_param->('force-cpu');\n        my $with_conntrack_state = $get_root_param->('with-conntrack-state');\n        my $nets_host_mtu = $get_root_param->('nets-host-mtu');\n\n        my $storagemap;\n\n        if ($targetstorage) {\n            raise_param_exc(\n                { targetstorage => \"targetstorage can only by used with migratedfrom.\" })\n                if !$migratedfrom;\n            $storagemap =\n                eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };\n            raise_param_exc({ targetstorage => \"failed to parse storage map: $@\" })\n                if $@;\n        }\n\n        # read spice ticket from STDIN\n        my $spice_ticket;\n        my $nbd_protocol_version = 0;\n        my $replicated_volumes = {};\n        my $offline_volumes = {};\n        if (\n            $stateuri\n            && ($stateuri eq 'tcp' || $stateuri eq 'unix')\n            && $migratedfrom\n            && ($rpcenv->{type} eq 'cli')\n        ) {\n            while (defined(my $line = <STDIN>)) {\n                chomp $line;\n                if ($line =~ m/^spice_ticket: (.+)$/) {\n                    $spice_ticket = $1;\n                } elsif ($line =~ m/^nbd_protocol_version: (\\d+)$/) {\n                    $nbd_protocol_version = $1;\n                } elsif ($line =~ m/^replicated_volume: (.*)$/) {\n                    $replicated_volumes->{$1} = 1;\n                } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead\n                    # TODO PVE 10.x drop special handling here\n                    $offline_volumes->{tpmstate0} = $1;\n                } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {\n                    $offline_volumes->{$1} = $2;\n                } elsif (!$spice_ticket) {\n                    # fallback for old source node\n                    $spice_ticket = $line;\n                } else {\n                    warn \"unknown 'start' parameter on STDIN: '$line'\\n\";\n                }\n            }\n        }\n\n        PVE::Cluster::check_cfs_quorum();\n\n        my $storecfg = PVE::Storage::config();\n\n        if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri && $rpcenv->{type} ne 'ha') {\n            my $hacmd = sub {\n                my $upid = shift;\n\n                print \"Requesting HA start for VM $vmid\\n\";\n\n                my $cmd = ['ha-manager', 'set', \"vm:$vmid\", '--state', 'started'];\n                run_command($cmd);\n                return;\n            };\n\n            return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);\n\n        } else {\n\n            my $realcmd = sub {\n                my $upid = shift;\n\n                syslog('info', \"start VM $vmid: $upid\\n\");\n\n                my $migrate_opts = {\n                    migratedfrom => $migratedfrom,\n                    spice_ticket => $spice_ticket,\n                    network => $migration_network,\n                    type => $migration_type,\n                    storagemap => $storagemap,\n                    nbd_proto_version => $nbd_protocol_version,\n                    replicated_volumes => $replicated_volumes,\n                    offline_volumes => $offline_volumes,\n                    with_conntrack_state => $with_conntrack_state,\n                };\n\n                my $params = {\n                    statefile => $stateuri,\n                    skiplock => $skiplock,\n                    forcemachine => $machine,\n                    timeout => $timeout,\n                    forcecpu => $force_cpu,\n                    'nets-host-mtu' => $nets_host_mtu,\n                };\n\n                PVE::QemuServer::vm_start($storecfg, $vmid, $params, $migrate_opts);\n                return;\n            };\n\n            return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);\n        }\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_stop',\n    path => '{vmid}/status/stop',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Stop virtual machine. The qemu process will exit immediately. This\"\n        . \" is akin to pulling the power plug of a running computer and may damage the VM data.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            migratedfrom => get_standard_option('pve-node', { optional => 1 }),\n            timeout => {\n                description => \"Wait maximal timeout seconds.\",\n                type => 'integer',\n                minimum => 0,\n                optional => 1,\n            },\n            keepActive => {\n                description => \"Do not deactivate storage volumes.\",\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n            },\n            'overrule-shutdown' => {\n                description => \"Try to abort active 'qmshutdown' tasks before stopping.\",\n                optional => 1,\n                type => 'boolean',\n                default => 0,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        my $keepActive = extract_param($param, 'keepActive');\n        raise_param_exc({ keepActive => \"Only root may use this option.\" })\n            if $keepActive && $authuser ne 'root@pam';\n\n        my $migratedfrom = extract_param($param, 'migratedfrom');\n        raise_param_exc({ migratedfrom => \"Only root may use this option.\" })\n            if $migratedfrom && $authuser ne 'root@pam';\n\n        my $overrule_shutdown = extract_param($param, 'overrule-shutdown');\n\n        my $storecfg = PVE::Storage::config();\n\n        if (\n            PVE::HA::Config::vm_is_ha_managed($vmid)\n            && ($rpcenv->{type} ne 'ha')\n            && !defined($migratedfrom)\n        ) {\n            raise_param_exc({ 'overrule-shutdown' => \"Not applicable for HA resources.\" })\n                if $overrule_shutdown;\n\n            my $hacmd = sub {\n                my $upid = shift;\n\n                print \"Requesting HA stop for VM $vmid\\n\";\n\n                my $cmd = ['ha-manager', 'crm-command', 'stop', \"vm:$vmid\", '0'];\n                run_command($cmd);\n                return;\n            };\n\n            return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);\n\n        } else {\n            my $realcmd = sub {\n                my $upid = shift;\n\n                syslog('info', \"stop VM $vmid: $upid\\n\");\n\n                if ($overrule_shutdown) {\n                    my $overruled_tasks =\n                        PVE::GuestHelpers::abort_guest_tasks($rpcenv, 'qmshutdown', $vmid);\n                    my $overruled_tasks_list = join(\", \", $overruled_tasks->@*);\n                    print \"overruled qmshutdown tasks: $overruled_tasks_list\\n\"\n                        if @$overruled_tasks;\n                }\n\n                PVE::QemuServer::vm_stop(\n                    $storecfg,\n                    $vmid,\n                    $skiplock,\n                    0,\n                    $param->{timeout},\n                    0,\n                    1,\n                    $keepActive,\n                    $migratedfrom,\n                );\n                return;\n            };\n\n            return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);\n        }\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_reset',\n    path => '{vmid}/status/reset',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Reset virtual machine.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            skiplock => get_standard_option('skiplock'),\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        die \"VM $vmid not running\\n\" if !PVE::QemuServer::check_running($vmid);\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            PVE::QemuServer::vm_reset($vmid, $skiplock);\n\n            return;\n        };\n\n        return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_shutdown',\n    path => '{vmid}/status/shutdown',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Shutdown virtual machine. This is similar to pressing the power button on a\"\n        . \" physical machine. This will send an ACPI event for the guest OS, which should then\"\n        . \" proceed to a clean shutdown.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            timeout => {\n                description => \"Wait maximal timeout seconds.\",\n                type => 'integer',\n                minimum => 0,\n                optional => 1,\n            },\n            forceStop => {\n                description => \"Make sure the VM stops.\",\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n            },\n            keepActive => {\n                description => \"Do not deactivate storage volumes.\",\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        my $keepActive = extract_param($param, 'keepActive');\n        raise_param_exc({ keepActive => \"Only root may use this option.\" })\n            if $keepActive && $authuser ne 'root@pam';\n\n        my $storecfg = PVE::Storage::config();\n\n        my $shutdown = 1;\n\n        # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when\n        # the VM gets resumed later, it still gets the request delivered and powers off\n        if (PVE::QemuServer::vm_is_paused($vmid, 1)) {\n            if ($param->{forceStop}) {\n                warn \"VM is paused - stop instead of shutdown\\n\";\n                $shutdown = 0;\n            } else {\n                die \"VM is paused - cannot shutdown\\n\";\n            }\n        }\n\n        if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {\n\n            my $timeout = $param->{timeout} // 60;\n            my $hacmd = sub {\n                my $upid = shift;\n\n                print \"Requesting HA stop for VM $vmid\\n\";\n\n                my $cmd = ['ha-manager', 'crm-command', 'stop', \"vm:$vmid\", \"$timeout\"];\n                run_command($cmd);\n                return;\n            };\n\n            return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);\n\n        } else {\n\n            my $realcmd = sub {\n                my $upid = shift;\n\n                syslog('info', \"shutdown VM $vmid: $upid\\n\");\n\n                PVE::QemuServer::vm_stop(\n                    $storecfg,\n                    $vmid,\n                    $skiplock,\n                    0,\n                    $param->{timeout},\n                    $shutdown,\n                    $param->{forceStop},\n                    $keepActive,\n                );\n                return;\n            };\n\n            return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);\n        }\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_reboot',\n    path => '{vmid}/status/reboot',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description =>\n        \"Reboot the VM by shutting it down, and starting it again. Applies pending changes.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            timeout => {\n                description => \"Wait maximal timeout seconds for the shutdown.\",\n                type => 'integer',\n                minimum => 0,\n                optional => 1,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n\n        die \"VM is paused - cannot shutdown\\n\" if PVE::QemuServer::vm_is_paused($vmid, 1);\n\n        die \"VM $vmid not running\\n\" if !PVE::QemuServer::check_running($vmid);\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            syslog('info', \"requesting reboot of VM $vmid: $upid\\n\");\n            PVE::QemuServer::vm_reboot($vmid, $param->{timeout});\n            return;\n        };\n\n        return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_suspend',\n    path => '{vmid}/status/suspend',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Suspend virtual machine.\",\n    permissions => {\n        description => \"You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',\"\n            . \" you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'\"\n            . \" on the storage for the vmstate.\",\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            todisk => {\n                type => 'boolean',\n                default => 0,\n                optional => 1,\n                description =>\n                    'If set, suspends the VM to disk. Will be resumed on next VM start.',\n            },\n            statestorage => get_standard_option(\n                'pve-storage-id',\n                {\n                    description => \"The storage for the VM state\",\n                    requires => 'todisk',\n                    optional => 1,\n                    completion => \\&PVE::Storage::complete_storage_enabled,\n                },\n            ),\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n\n        my $todisk = extract_param($param, 'todisk') // 0;\n\n        my $statestorage = extract_param($param, 'statestorage');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        die \"VM $vmid not running\\n\" if !PVE::QemuServer::check_running($vmid);\n\n        die \"Cannot suspend HA managed VM to disk\\n\"\n            if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);\n\n        # early check for storage permission, for better user feedback\n        if ($todisk) {\n            $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            # cannot save the state of a non-virtualized PCIe device, so resume cannot really work\n            for my $key (keys %$conf) {\n                next if $key !~ /^hostpci\\d+/;\n                die\n                    \"cannot suspend VM to disk due to passed-through PCI device(s), which lack the\"\n                    . \" possibility to save/restore their internal state\\n\";\n            }\n\n            if (!$statestorage) {\n                # get statestorage from config if none is given\n                my $storecfg = PVE::Storage::config();\n                $statestorage = PVE::QemuConfig::find_vmstate_storage($conf, $storecfg);\n            }\n\n            $rpcenv->check($authuser, \"/storage/$statestorage\", ['Datastore.AllocateSpace']);\n        }\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            syslog('info', \"suspend VM $vmid: $upid\\n\");\n\n            PVE::QemuServer::RunState::vm_suspend($vmid, $skiplock, $todisk, $statestorage);\n\n            return;\n        };\n\n        my $taskname = $todisk ? 'qmsuspend' : 'qmpause';\n        return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_resume',\n    path => '{vmid}/status/resume',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Resume virtual machine.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.PowerMgmt']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            nocheck => { type => 'boolean', optional => 1 },\n\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        # nocheck is used as part of migration when config file might be still\n        # be on source node\n        my $nocheck = extract_param($param, 'nocheck');\n        raise_param_exc({ nocheck => \"Only root may use this option.\" })\n            if $nocheck && $authuser ne 'root@pam';\n\n        my $to_disk_suspended;\n        eval {\n            PVE::QemuConfig->lock_config(\n                $vmid,\n                sub {\n                    my $conf = PVE::QemuConfig->load_config($vmid);\n                    $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');\n                },\n            );\n        };\n\n        die \"VM $vmid not running\\n\"\n            if !$to_disk_suspended && !PVE::QemuServer::check_running($vmid, $nocheck);\n\n        my $realcmd = sub {\n            my $upid = shift;\n\n            syslog('info', \"resume VM $vmid: $upid\\n\");\n\n            if (!$to_disk_suspended) {\n                PVE::QemuServer::RunState::vm_resume($vmid, $skiplock, $nocheck);\n            } else {\n                my $storecfg = PVE::Storage::config();\n                PVE::QemuServer::vm_start($storecfg, $vmid, { skiplock => $skiplock });\n            }\n\n            return;\n        };\n\n        return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_sendkey',\n    path => '{vmid}/sendkey',\n    method => 'PUT',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Send key event to virtual machine.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Console']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            skiplock => get_standard_option('skiplock'),\n            key => {\n                description => \"The key (qemu monitor encoding).\",\n                type => 'string',\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_feature',\n    path => '{vmid}/feature',\n    method => 'GET',\n    proxyto => 'node',\n    protected => 1,\n    description => \"Check if feature for virtual machine is available.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            feature => {\n                description => \"Feature to check.\",\n                type => 'string',\n                enum => ['snapshot', 'clone', 'copy'],\n            },\n            snapname => get_standard_option('pve-snapshot-name', {\n                    optional => 1,\n            }),\n        },\n    },\n    returns => {\n        type => \"object\",\n        properties => {\n            hasFeature => { type => 'boolean' },\n            nodes => {\n                type => 'array',\n                items => { type => 'string' },\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $snapname = extract_param($param, 'snapname');\n\n        my $feature = extract_param($param, 'feature');\n\n        my $running = PVE::QemuServer::check_running($vmid);\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        if ($snapname) {\n            my $snap = $conf->{snapshots}->{$snapname};\n            die \"snapshot '$snapname' does not exist\\n\" if !defined($snap);\n            $conf = $snap;\n        }\n        my $storecfg = PVE::Storage::config();\n\n        my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);\n        my $hasFeature =\n            PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);\n\n        return {\n            hasFeature => $hasFeature,\n            nodes => [keys %$nodelist],\n        };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'clone_vm',\n    path => '{vmid}/clone',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Create a copy of virtual machine/template.\",\n    permissions => {\n        description =>\n            \"You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions \"\n            . \"on /vms/{newid} (or on the VM pool /pool/{pool}). You also need \"\n            . \"'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet\",\n        check => [\n            'and',\n            ['perm', '/vms/{vmid}', ['VM.Clone']],\n            [\n                'or',\n                ['perm', '/vms/{newid}', ['VM.Allocate']],\n                ['perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],\n            ],\n        ],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            newid => get_standard_option(\n                'pve-vmid',\n                {\n                    completion => \\&PVE::Cluster::complete_next_vmid,\n                    description => 'VMID for the clone.',\n                },\n            ),\n            name => {\n                optional => 1,\n                type => 'string',\n                format => 'dns-name',\n                description => \"Set a name for the new VM.\",\n            },\n            description => {\n                optional => 1,\n                type => 'string',\n                description => \"Description for the new VM.\",\n            },\n            pool => {\n                optional => 1,\n                type => 'string',\n                format => 'pve-poolid',\n                description => \"Add the new VM to the specified pool.\",\n            },\n            snapname => get_standard_option('pve-snapshot-name', {\n                    optional => 1,\n            }),\n            storage => get_standard_option(\n                'pve-storage-id',\n                {\n                    description => \"Target storage for full clone.\",\n                    optional => 1,\n                },\n            ),\n            'format' => {\n                description => \"Target format for file storage. Only valid for full clone.\",\n                type => 'string',\n                optional => 1,\n                enum => ['raw', 'qcow2', 'vmdk'],\n            },\n            full => {\n                optional => 1,\n                type => 'boolean',\n                description => \"Create a full copy of all disks. This is always done when \"\n                    . \"you clone a normal VM. For VM templates, we try to create a linked clone by default.\",\n            },\n            target => get_standard_option(\n                'pve-node',\n                {\n                    description =>\n                        \"Target node. Only allowed if the original VM is on shared storage.\",\n                    optional => 1,\n                },\n            ),\n            bwlimit => {\n                description => \"Override I/O bandwidth limit (in KiB/s).\",\n                optional => 1,\n                type => 'integer',\n                minimum => '0',\n                default => 'clone limit from datacenter or storage config',\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n        my $newid = extract_param($param, 'newid');\n        my $pool = extract_param($param, 'pool');\n\n        my $snapname = extract_param($param, 'snapname');\n        my $storage = extract_param($param, 'storage');\n        my $format = extract_param($param, 'format');\n        my $target = extract_param($param, 'target');\n\n        my $localnode = PVE::INotify::nodename();\n\n        if ($target && ($target eq $localnode || $target eq 'localhost')) {\n            undef $target;\n        }\n\n        my $running = PVE::QemuServer::check_running($vmid) || 0;\n\n        my $load_and_check = sub {\n            $rpcenv->check_pool_exist($pool) if defined($pool);\n            PVE::Cluster::check_node_exists($target) if $target;\n\n            my $storecfg = PVE::Storage::config();\n\n            if ($storage) {\n                # check if storage is enabled on local node and supports vm images\n                my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storage);\n                raise_param_exc({ storage => \"storage '$storage' does not support vm images\" })\n                    if !$scfg->{content}->{images};\n\n                if ($target) {\n                    # check if storage is available on target node\n                    PVE::Storage::storage_check_enabled($storecfg, $storage, $target);\n                    # clone only works if target storage is shared\n                    die \"can't clone to non-shared storage '$storage'\\n\"\n                        if !$scfg->{shared};\n                }\n            }\n\n            PVE::Cluster::check_cfs_quorum();\n\n            my $conf = PVE::QemuConfig->load_config($vmid);\n            PVE::QemuConfig->check_lock($conf);\n\n            my $verify_running = PVE::QemuServer::check_running($vmid) || 0;\n            die \"unexpected state change\\n\" if $verify_running != $running;\n\n            die \"snapshot '$snapname' does not exist\\n\"\n                if $snapname && !defined($conf->{snapshots}->{$snapname});\n\n            my $full = $param->{full} // !PVE::QemuConfig->is_template($conf);\n\n            die \"parameter 'storage' not allowed for linked clones\\n\"\n                if defined($storage) && !$full;\n\n            die \"parameter 'format' not allowed for linked clones\\n\"\n                if defined($format) && !$full;\n\n            my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;\n\n            my $sharedvm =\n                &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);\n            PVE::QemuServer::check_mapping_access($rpcenv, $authuser, $oldconf);\n\n            PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $oldconf);\n\n            die \"can't clone VM to node '$target' (VM uses local storage)\\n\"\n                if $target && !$sharedvm;\n\n            my $conffile = PVE::QemuConfig->config_file($newid);\n            die \"unable to create VM $newid: config file already exists\\n\"\n                if -f $conffile;\n\n            my $newconf = { lock => 'clone' };\n            my $drives = {};\n            my $fullclone = {};\n            my $vollist = [];\n\n            for my $opt (sort keys %$oldconf) {\n                my $value = $oldconf->{$opt};\n\n                # do not copy snapshot related info\n                next\n                    if $opt eq 'snapshots'\n                    || $opt eq 'parent'\n                    || $opt eq 'snaptime'\n                    || $opt eq 'vmstate'\n                    || $opt eq 'snapstate'\n                    || $opt eq 'runningcpu'\n                    || $opt eq 'runningmachine'\n                    || $opt eq 'running-nets-host-mtu';\n\n                # no need to copy unused images, because VMID(owner) changes anyways\n                next if $opt =~ m/^unused\\d+$/;\n\n                die \"cannot clone TPM state while VM is running\\n\"\n                    if $full && $running && !$snapname && $opt eq 'tpmstate0';\n\n                # always change MAC! address\n                if ($opt =~ m/^net(\\d+)$/) {\n                    my $net = PVE::QemuServer::Network::parse_net($value);\n                    my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');\n                    $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});\n                    $newconf->{$opt} = PVE::QemuServer::Network::print_net($net);\n                } elsif (PVE::QemuServer::is_valid_drivename($opt)) {\n                    my $drive = PVE::QemuServer::parse_drive($opt, $value);\n                    die \"unable to parse drive options for '$opt'\\n\" if !$drive;\n                    if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {\n                        $newconf->{$opt} = $value; # simply copy configuration\n                    } else {\n                        my $volid = $drive->{file};\n                        my $msg = \"clone feature is not supported for\";\n                        $msg .= \" a snapshot of\" if $snapname;\n                        $msg .= \" '$volid' ($opt)\";\n                        if (\n                            $full\n                            || PVE::QemuServer::drive_is_cloudinit($drive)\n                            || $opt eq 'tpmstate0'\n                        ) {\n                            die \"Full $msg\\n\"\n                                if !PVE::Storage::volume_has_feature(\n                                    $storecfg,\n                                    'copy',\n                                    $volid,\n                                    $snapname,\n                                    $running,\n                                );\n                            $fullclone->{$opt} = 1;\n                        } else {\n                            # not full means clone instead of copy\n                            die \"Linked $msg\\n\"\n                                if !PVE::Storage::volume_has_feature(\n                                    $storecfg,\n                                    'clone',\n                                    $volid,\n                                    $snapname,\n                                    $running,\n                                );\n                        }\n                        $drives->{$opt} = $drive;\n                        next if PVE::QemuServer::drive_is_cloudinit($drive);\n                        push @$vollist, $volid;\n                    }\n                } else {\n                    # copy everything else\n                    $newconf->{$opt} = $value;\n                }\n            }\n\n            return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);\n        };\n\n        my $clonefn = sub {\n            my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) =\n                $load_and_check->();\n\n            print(\"creating a clone of VM $vmid with ID $newid\\n\");\n\n            my $storecfg = PVE::Storage::config();\n\n            # auto generate a new uuid\n            my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');\n            $smbios1->{uuid} = PVE::QemuServer::generate_uuid();\n            $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);\n            # auto generate a new vmgenid only if the option was set for template\n            if ($newconf->{vmgenid}) {\n                $newconf->{vmgenid} = PVE::QemuServer::generate_uuid();\n            }\n\n            delete $newconf->{template};\n\n            if ($param->{name}) {\n                $newconf->{name} = $param->{name};\n            } else {\n                $newconf->{name} = \"Copy-of-VM-\" . ($oldconf->{name} // $vmid);\n            }\n\n            if ($param->{description}) {\n                $newconf->{description} = $param->{description};\n            }\n\n            # create empty/temp config - this fails if VM already exists on other node\n            # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code\n            PVE::Tools::file_set_contents($conffile, \"# qmclone temporary file\\nlock: clone\\n\");\n\n            PVE::Firewall::clone_vmfw_conf($vmid, $newid);\n\n            my $newvollist = [];\n            my $jobs = {};\n\n            eval {\n                local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n                    sub { die \"interrupted by signal\\n\"; };\n\n                PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);\n\n                my $bwlimit = extract_param($param, 'bwlimit');\n\n                my $total_jobs = scalar(keys %{$drives});\n                my $i = 1;\n\n                foreach my $opt (sort keys %$drives) {\n                    my $drive = $drives->{$opt};\n                    my $skipcomplete = ($total_jobs != $i); # finish after last drive\n                    my $completion = $skipcomplete ? 'skip' : 'complete';\n\n                    my $src_sid = PVE::Storage::parse_volume_id($drive->{file});\n                    my $storage_list = [$src_sid];\n                    push @$storage_list, $storage if defined($storage);\n                    my $clonelimit =\n                        PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);\n\n                    my $source_info = {\n                        vmid => $vmid,\n                        running => $running,\n                        drivename => $opt,\n                        drive => $drive,\n                        snapname => $snapname,\n                    };\n\n                    my $dest_info = {\n                        vmid => $newid,\n                        drivename => $opt,\n                        storage => $storage,\n                        format => $format,\n                    };\n\n                    $dest_info->{efisize} = PVE::QemuServer::get_efivars_size($oldconf)\n                        if $opt eq 'efidisk0';\n\n                    my $fs_freeze = PVE::QemuServer::Agent::should_fs_freeze($oldconf);\n\n                    my $newdrive = PVE::QemuServer::clone_disk(\n                        $storecfg,\n                        $source_info,\n                        $dest_info,\n                        $fullclone->{$opt},\n                        $newvollist,\n                        $jobs,\n                        $completion,\n                        $fs_freeze,\n                        $clonelimit,\n                    );\n\n                    $newconf->{$opt} = PVE::QemuServer::print_drive($newdrive);\n\n                    PVE::QemuConfig->write_config($newid, $newconf);\n                    $i++;\n                }\n\n                delete $newconf->{lock};\n\n                # do not write pending changes\n                if (my @changes = keys %{ $newconf->{pending} }) {\n                    my $pending = join(',', @changes);\n                    warn \"found pending changes for '$pending', discarding for clone\\n\";\n                    delete $newconf->{pending};\n                }\n\n                PVE::QemuConfig->write_config($newid, $newconf);\n\n                PVE::QemuServer::Network::create_ifaces_ipams_ips($newconf, $newid);\n\n                if ($target) {\n                    if (!$running) {\n                        # always deactivate volumes - avoids that LVM LVs are active on several nodes\n                        eval {\n                            PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname);\n                        };\n                        # but only warn when that fails (e.g., parallel clones keeping them active)\n                        log_warn($@) if $@;\n                    }\n\n                    PVE::Storage::deactivate_volumes($storecfg, $newvollist);\n\n                    my $newconffile = PVE::QemuConfig->config_file($newid, $target);\n                    die \"Failed to move config to node '$target' - rename failed: $!\\n\"\n                        if !rename($conffile, $newconffile);\n                }\n\n                PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;\n            };\n            if (my $err = $@) {\n                eval {\n                    PVE::QemuServer::BlockJob::qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);\n                };\n                sleep 1; # some storage like rbd need to wait before release volume - really?\n\n                foreach my $volid (@$newvollist) {\n                    eval { PVE::Storage::vdisk_free($storecfg, $volid); };\n                    warn $@ if $@;\n                }\n\n                PVE::Firewall::remove_vmfw_conf($newid);\n\n                unlink $conffile; # avoid races -> last thing before die\n\n                die \"clone failed: $err\";\n            }\n\n            return;\n        };\n\n        # Acquire exclusive lock lock for $newid\n        my $lock_target_vm = sub {\n            return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);\n        };\n\n        my $lock_source_vm = sub {\n            # exclusive lock if VM is running - else shared lock is enough;\n            if ($running) {\n                return PVE::QemuConfig->lock_config_full($vmid, 1, $lock_target_vm);\n            } else {\n                return PVE::QemuConfig->lock_config_shared($vmid, 1, $lock_target_vm);\n            }\n        };\n\n        $load_and_check->(); # early checks before forking/locking\n\n        return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'move_vm_disk',\n    path => '{vmid}/move_disk',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Move volume to different storage or to a different VM.\",\n    permissions => {\n        description => \"You need 'VM.Config.Disk' permissions on /vms/{vmid}, \"\n            . \"and 'Datastore.AllocateSpace' permissions on the storage. To move \"\n            . \"a disk to another VM, you need the permissions on the target VM as well.\",\n        check => ['perm', '/vms/{vmid}', ['VM.Config.Disk']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            'target-vmid' => get_standard_option(\n                'pve-vmid',\n                {\n                    completion => \\&PVE::QemuServer::complete_vmid,\n                    optional => 1,\n                },\n            ),\n            disk => {\n                type => 'string',\n                description => \"The disk you want to move.\",\n                enum => [PVE::QemuServer::Drive::valid_drive_names_with_unused()],\n            },\n            storage => get_standard_option(\n                'pve-storage-id',\n                {\n                    description => \"Target storage.\",\n                    completion => \\&PVE::QemuServer::complete_storage,\n                    optional => 1,\n                },\n            ),\n            'format' => {\n                type => 'string',\n                description => \"Target Format.\",\n                enum => ['raw', 'qcow2', 'vmdk'],\n                optional => 1,\n            },\n            delete => {\n                type => 'boolean',\n                description => \"Delete the original disk after successful copy. By default the\"\n                    . \" original disk is kept as unused disk.\",\n                optional => 1,\n                default => 0,\n            },\n            digest => {\n                type => 'string',\n                description =>\n                    'Prevent changes if current configuration file has different SHA1'\n                    . ' digest. This can be used to prevent concurrent modifications.',\n                maxLength => 40,\n                optional => 1,\n            },\n            bwlimit => {\n                description => \"Override I/O bandwidth limit (in KiB/s).\",\n                optional => 1,\n                type => 'integer',\n                minimum => '0',\n                default => 'move limit from datacenter or storage config',\n            },\n            'target-disk' => {\n                type => 'string',\n                description => \"The config key the disk will be moved to on the target VM\"\n                    . \" (for example, ide0 or scsi1). Default is the source disk key.\",\n                enum => [PVE::QemuServer::Drive::valid_drive_names_with_unused()],\n                optional => 1,\n            },\n            'target-digest' => {\n                type => 'string',\n                description =>\n                    'Prevent changes if the current config file of the target VM has a'\n                    . ' different SHA1 digest. This can be used to detect concurrent modifications.',\n                maxLength => 40,\n                optional => 1,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n        my $target_vmid = extract_param($param, 'target-vmid');\n        my $digest = extract_param($param, 'digest');\n        my $target_digest = extract_param($param, 'target-digest');\n        my $disk = extract_param($param, 'disk');\n        my $target_disk = extract_param($param, 'target-disk') // $disk;\n        my $storeid = extract_param($param, 'storage');\n        my $format = extract_param($param, 'format');\n\n        my $storecfg = PVE::Storage::config();\n\n        my $load_and_check_move = sub {\n            my $conf = PVE::QemuConfig->load_config($vmid);\n            PVE::QemuConfig->check_lock($conf);\n\n            PVE::Tools::assert_if_modified($digest, $conf->{digest});\n\n            die \"disk '$disk' does not exist\\n\" if !$conf->{$disk};\n\n            my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});\n\n            die \"disk '$disk' has no associated volume\\n\" if !$drive->{file};\n            die \"you can't move a cdrom\\n\" if PVE::QemuServer::drive_is_cdrom($drive, 1);\n\n            my $old_volid = $drive->{file};\n            my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);\n            my $oldfmt = (PVE::Storage::parse_volname($storecfg, $old_volid))[6];\n\n            die \"you can't move to the same storage with same format\\n\"\n                if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);\n\n            my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid);\n            raise_param_exc({ storage => \"storage '$storeid' does not support vm images\" })\n                if !$scfg->{content}->{images};\n\n            # this only checks snapshots because $disk is passed!\n            my $snapshotted = PVE::QemuServer::Drive::is_volume_in_use(\n                $storecfg, $conf, $disk, $old_volid,\n            );\n            die \"you can't move a disk with snapshots and delete the source\\n\"\n                if $snapshotted && $param->{delete};\n\n            return ($conf, $drive, $oldstoreid, $snapshotted);\n        };\n\n        my $move_updatefn = sub {\n            my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();\n            my $old_volid = $drive->{file};\n\n            PVE::Cluster::log_msg(\n                'info',\n                $authuser,\n                \"move disk VM $vmid: move --disk $disk --storage $storeid\",\n            );\n\n            print(\"copying volume '$old_volid' from current storage '$oldstoreid' to target\"\n                . \" storage '$storeid'\\n\");\n\n            my $running = PVE::QemuServer::check_running($vmid);\n\n            PVE::Storage::activate_volumes($storecfg, [$drive->{file}]);\n\n            my $newvollist = [];\n\n            eval {\n                local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n                    sub { die \"interrupted by signal\\n\"; };\n\n                warn \"moving disk with snapshots, snapshots will not be moved!\\n\"\n                    if $snapshotted;\n\n                my $bwlimit = extract_param($param, 'bwlimit');\n                my $movelimit = PVE::Storage::get_bandwidth_limit(\n                    'move', [$oldstoreid, $storeid], $bwlimit,\n                );\n\n                my $source_info = {\n                    vmid => $vmid,\n                    running => $running,\n                    drivename => $disk,\n                    drive => $drive,\n                    snapname => undef,\n                };\n\n                my $dest_info = {\n                    vmid => $vmid,\n                    drivename => $disk,\n                    storage => $storeid,\n                    format => $format,\n                };\n\n                $dest_info->{efisize} = PVE::QemuServer::get_efivars_size($conf)\n                    if $disk eq 'efidisk0';\n\n                my $newdrive = PVE::QemuServer::clone_disk(\n                    $storecfg,\n                    $source_info,\n                    $dest_info,\n                    1,\n                    $newvollist,\n                    undef,\n                    undef,\n                    undef,\n                    $movelimit,\n                );\n                $conf->{$disk} = PVE::QemuServer::print_drive($newdrive);\n\n                if (!$param->{delete}) {\n                    my $unused_key = PVE::QemuConfig->add_unused_volume($conf, $old_volid);\n                    print(\"adding source volume '$old_volid' as '$unused_key' to config\\n\");\n                }\n\n                # convert moved disk to base if part of template\n                PVE::QemuServer::template_create($vmid, $conf, $disk)\n                    if PVE::QemuConfig->is_template($conf);\n\n                PVE::QemuConfig->write_config($vmid, $conf);\n\n                my $do_trim = PVE::QemuServer::Agent::get_qga_key($conf, 'fstrim_cloned_disks');\n                if ($running && $do_trim && PVE::QemuServer::Agent::qga_check_running($vmid)) {\n                    eval { mon_cmd($vmid, \"guest-fstrim\") };\n                }\n\n                eval {\n                    # try to deactivate volumes - avoid lvm LVs to be active on several nodes\n                    PVE::Storage::deactivate_volumes($storecfg, [$newdrive->{file}])\n                        if !$running;\n                };\n                warn $@ if $@;\n            };\n            if (my $err = $@) {\n                foreach my $volid (@$newvollist) {\n                    eval { PVE::Storage::vdisk_free($storecfg, $volid) };\n                    warn $@ if $@;\n                }\n                die \"storage migration failed: $err\";\n            }\n\n            if ($param->{delete}) {\n                print(\"removing source volume '$old_volid' after successful copy\\n\");\n                eval {\n                    PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);\n                    PVE::Storage::vdisk_free($storecfg, $old_volid);\n                };\n                warn $@ if $@;\n            }\n        };\n\n        my $load_and_check_reassign_configs = sub {\n            my $vmlist = PVE::Cluster::get_vmlist()->{ids};\n\n            die \"could not find VM ${vmid}\\n\" if !exists($vmlist->{$vmid});\n            die \"could not find target VM ${target_vmid}\\n\" if !exists($vmlist->{$target_vmid});\n\n            my $source_node = $vmlist->{$vmid}->{node};\n            my $target_node = $vmlist->{$target_vmid}->{node};\n\n            die \"Both VMs need to be on the same node ($source_node != $target_node)\\n\"\n                if $source_node ne $target_node;\n\n            my $source_conf = PVE::QemuConfig->load_config($vmid);\n            PVE::QemuConfig->check_lock($source_conf);\n            my $target_conf = PVE::QemuConfig->load_config($target_vmid);\n            PVE::QemuConfig->check_lock($target_conf);\n\n            die \"Can't move disks from or to template VMs\\n\"\n                if ($source_conf->{template} || $target_conf->{template});\n\n            if ($digest) {\n                eval { PVE::Tools::assert_if_modified($digest, $source_conf->{digest}) };\n                die \"VM ${vmid}: $@\" if $@;\n            }\n\n            if ($target_digest) {\n                eval { PVE::Tools::assert_if_modified($target_digest, $target_conf->{digest}) };\n                die \"VM ${target_vmid}: $@\" if $@;\n            }\n\n            die \"Disk '${disk}' for VM '$vmid' does not exist\\n\"\n                if !defined($source_conf->{$disk});\n\n            die \"Target disk key '${target_disk}' is already in use for VM '$target_vmid'\\n\"\n                if $target_conf->{$target_disk};\n\n            my $drive = PVE::QemuServer::parse_drive(\n                $disk, $source_conf->{$disk},\n            );\n            die \"failed to parse source disk - $@\\n\" if !$drive;\n\n            my $source_volid = $drive->{file};\n\n            die \"disk '${disk}' has no associated volume\\n\" if !$source_volid;\n            die \"CD drive contents can't be moved to another VM\\n\"\n                if PVE::QemuServer::drive_is_cdrom($drive, 1);\n\n            my $storeid = PVE::Storage::parse_volume_id($source_volid, 1);\n            die \"Volume '$source_volid' not managed by PVE\\n\" if !defined($storeid);\n\n            die \"Can't move disk used by a snapshot to another VM\\n\"\n                if PVE::QemuServer::Drive::is_volume_in_use(\n                    $storecfg, $source_conf, $disk, $source_volid,\n                );\n            die \"Storage does not support moving of this disk to another VM\\n\"\n                if (!PVE::Storage::volume_has_feature($storecfg, 'rename', $source_volid));\n            die \"Cannot move disk to another VM while the source VM is running - detach first\\n\"\n                if PVE::QemuServer::check_running($vmid) && $disk !~ m/^unused\\d+$/;\n\n            # now re-parse using target disk slot format\n            if ($target_disk =~ /^unused\\d+$/) {\n                $drive = PVE::QemuServer::parse_drive(\n                    $target_disk, $source_volid,\n                );\n            } else {\n                $drive = PVE::QemuServer::parse_drive(\n                    $target_disk, $source_conf->{$disk},\n                );\n            }\n            die \"failed to parse source disk for target disk format - $@\\n\" if !$drive;\n\n            my $repl_conf = PVE::ReplicationConfig->new();\n            if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {\n                my $format = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];\n                die\n                    \"Cannot move disk to a replicated VM. Storage does not support replication!\\n\"\n                    if !PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);\n            }\n\n            return ($source_conf, $target_conf, $drive);\n        };\n\n        my $logfunc = sub {\n            my ($msg) = @_;\n            print STDERR \"$msg\\n\";\n        };\n\n        my $disk_reassignfn = sub {\n            return PVE::QemuConfig->lock_config(\n                $vmid,\n                sub {\n                    return PVE::QemuConfig->lock_config(\n                        $target_vmid,\n                        sub {\n                            my ($source_conf, $target_conf, $drive) =\n                                &$load_and_check_reassign_configs();\n\n                            my $source_volid = $drive->{file};\n\n                            print(\"reassign disk '$disk' from VM '$vmid' as '$target_disk' to\"\n                                . \" VM '$target_vmid'\\n\");\n                            my ($storeid, $source_volname) =\n                                PVE::Storage::parse_volume_id($source_volid);\n\n                            my $fmt =\n                                (PVE::Storage::parse_volname($storecfg, $source_volid))[6];\n\n                            my $new_volid = PVE::Storage::rename_volume(\n                                $storecfg, $source_volid, $target_vmid,\n                            );\n\n                            $drive->{file} = $new_volid;\n\n                            my $boot_order = PVE::QemuServer::device_bootorder($source_conf);\n                            if (defined(delete $boot_order->{$disk})) {\n                                print \"removing disk '$disk' from boot order config\\n\";\n                                my $boot_devs = [\n                                    sort { $boot_order->{$a} <=> $boot_order->{$b} }\n                                        keys %$boot_order\n                                ];\n                                $source_conf->{boot} =\n                                    PVE::QemuServer::print_bootorder($boot_devs);\n                            }\n\n                            delete $source_conf->{$disk};\n                            print \"removing disk '${disk}' from VM '${vmid}' config\\n\";\n                            PVE::QemuConfig->write_config($vmid, $source_conf);\n\n                            my $drive_string = PVE::QemuServer::print_drive($drive);\n\n                            if ($target_disk =~ /^unused\\d+$/) {\n                                $target_conf->{$target_disk} = $drive_string;\n                                PVE::QemuConfig->write_config($target_vmid, $target_conf);\n                            } else {\n                                &$update_vm_api(\n                                    {\n                                        node => $node,\n                                        vmid => $target_vmid,\n                                        digest => $target_digest,\n                                        $target_disk => $drive_string,\n                                    },\n                                    1,\n                                );\n                            }\n\n                            # remove possible replication snapshots\n                            if (\n                                PVE::Storage::volume_has_feature(\n                                    $storecfg,\n                                    'replicate',\n                                    $source_volid,\n                                ),\n                            ) {\n                                eval {\n                                    PVE::Replication::prepare(\n                                        $storecfg, [$new_volid], undef, 1, undef, $logfunc,\n                                    );\n                                };\n                                if (my $err = $@) {\n                                    print\n                                        \"Failed to remove replication snapshots on moved disk \"\n                                        . \"'$target_disk'. Manual cleanup could be necessary.\\n\";\n                                }\n                            }\n                        },\n                    );\n                },\n            );\n        };\n\n        if ($target_vmid && $storeid) {\n            my $msg = \"either set 'storage' or 'target-vmid', but not both\";\n            raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });\n        } elsif ($target_vmid) {\n            $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])\n                if $authuser ne 'root@pam';\n\n            raise_param_exc(\n                { 'target-vmid' => \"must be different than source VMID to reassign disk\" })\n                if $vmid eq $target_vmid;\n\n            my (undef, undef, $drive) = &$load_and_check_reassign_configs();\n            my $storage = PVE::Storage::parse_volume_id($drive->{file});\n            $rpcenv->check($authuser, \"/storage/$storage\", ['Datastore.AllocateSpace']);\n\n            return $rpcenv->fork_worker(\n                'qmmove',\n                \"${vmid}-${disk}>${target_vmid}-${target_disk}\",\n                $authuser,\n                $disk_reassignfn,\n            );\n        } elsif ($storeid) {\n            $rpcenv->check($authuser, \"/storage/$storeid\", ['Datastore.AllocateSpace']);\n\n            $load_and_check_move->(); # early checks before forking/locking\n\n            my $realcmd = sub {\n                PVE::QemuConfig->lock_config($vmid, $move_updatefn);\n            };\n\n            return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);\n        } else {\n            my $msg = \"both 'storage' and 'target-vmid' missing, either needs to be set\";\n            raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });\n        }\n    },\n});\n\nmy $check_vm_disks_local = sub {\n    my ($storecfg, $vmconf, $vmid) = @_;\n\n    my $local_disks = {};\n\n    # add some more information to the disks e.g. cdrom\n    PVE::QemuServer::foreach_volid(\n        $vmconf,\n        sub {\n            my ($volid, $attr) = @_;\n\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n            if ($storeid) {\n                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n                return if $scfg->{shared};\n            }\n            # The shared attr here is just a special case where the vdisk\n            # is marked as shared manually\n            return if $attr->{shared};\n            return if $attr->{cdrom} and $volid eq \"none\";\n\n            if (exists $local_disks->{$volid}) {\n                @{ $local_disks->{$volid} }{ keys %$attr } = values %$attr;\n            } else {\n                $local_disks->{$volid} = $attr;\n                # ensure volid is present in case it's needed\n                $local_disks->{$volid}->{volid} = $volid;\n            }\n        },\n    );\n\n    return $local_disks;\n};\n\n__PACKAGE__->register_method({\n    name => 'migrate_vm_precondition',\n    path => '{vmid}/migrate',\n    method => 'GET',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Get preconditions for migration.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Migrate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            target => get_standard_option(\n                'pve-node',\n                {\n                    description => \"Target node.\",\n                    completion => \\&PVE::Cluster::complete_migration_target,\n                    optional => 1,\n                },\n            ),\n        },\n    },\n    returns => {\n        # TODO 9.x: rework the api call to return more sensible structures\n        # e.g. a simple list of nodes with their blockers and/or notices to show\n        type => \"object\",\n        properties => {\n            running => {\n                type => 'boolean',\n                description => \"Determines if the VM is running.\",\n            },\n            allowed_nodes => {\n                type => 'array',\n                items => {\n                    type => 'string',\n                    description => \"An allowed node\",\n                },\n                optional => 1,\n                description => \"List of nodes allowed for migration.\",\n            },\n            not_allowed_nodes => {\n                type => 'object',\n                optional => 1,\n                properties => {\n                    unavailable_storages => {\n                        type => 'array',\n                        optional => 1,\n                        description => 'A list of not available storages.',\n                        items => {\n                            type => 'string',\n                            description => 'A storage',\n                        },\n                    },\n                    'blocking-ha-resources' => {\n                        description => \"HA resources, which are blocking the\"\n                            . \" VM from being migrated to the node.\",\n                        type => 'array',\n                        optional => 1,\n                        items => {\n                            description => \"A blocking HA resource\",\n                            type => 'object',\n                            properties => {\n                                sid => {\n                                    type => 'string',\n                                    description => \"The blocking HA resource id\",\n                                },\n                                cause => {\n                                    type => 'string',\n                                    description => \"The reason why the HA\"\n                                        . \" resource is blocking the migration.\",\n                                    enum => ['resource-affinity'],\n                                },\n                            },\n                        },\n                    },\n                },\n                description => \"List of not allowed nodes with additional information.\",\n            },\n            local_disks => {\n                type => 'array',\n                items => {\n                    type => 'object',\n                    properties => {\n                        size => {\n                            type => 'integer',\n                            description => 'The size of the disk in bytes.',\n                        },\n                        volid => {\n                            type => 'string',\n                            description => 'The volid of the disk.',\n                        },\n                        cdrom => {\n                            type => 'boolean',\n                            description => 'True if the disk is a cdrom.',\n                        },\n                        is_unused => {\n                            type => 'boolean',\n                            description => 'True if the disk is unused.',\n                        },\n                    },\n                },\n                description =>\n                    \"List local disks including CD-Rom, unused and not referenced disks\",\n            },\n            local_resources => {\n                type => 'array',\n                items => {\n                    type => 'string',\n                    description => \"A local resource\",\n                },\n                description => \"List local resources (e.g. pci, usb) that block migration.\",\n            },\n            # FIXME: remove with 9.0\n            'mapped-resources' => {\n                type => 'array',\n                items => {\n                    type => 'string',\n                    description => \"A mapped resource\",\n                },\n                description =>\n                    \"List of mapped resources e.g. pci, usb. Deprecated, use 'mapped-resource-info' instead.\",\n            },\n            'mapped-resource-info' => {\n                type => 'object',\n                description =>\n                    \"Object of mapped resources with additional information such if they're live migratable.\",\n            },\n            'has-dbus-vmstate' => {\n                type => 'boolean',\n                description => 'Whether the VM host supports migrating additional VM state, '\n                    . 'such as conntrack entries.',\n            },\n            'dependent-ha-resources' => {\n                description => \"HA resources, which will be migrated to the same target node as\"\n                    . \" the VM, because these are in positive affinity with the VM.\",\n                type => 'array',\n                optional => 1,\n                items => {\n                    type => 'string',\n                    description => \"The '<ty>:<id>' resource IDs of a HA resource with a\"\n                        . \" positive affinity rule to this VM.\",\n                },\n            },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        PVE::Cluster::check_cfs_quorum();\n\n        my $res = {};\n\n        my $vmid = extract_param($param, 'vmid');\n        my $target = extract_param($param, 'target');\n        my $localnode = PVE::INotify::nodename();\n\n        # test if VM exists\n        my $vmconf = PVE::QemuConfig->load_config($vmid);\n        my $storecfg = PVE::Storage::config();\n\n        # try to detect errors early\n        PVE::QemuConfig->check_lock($vmconf);\n\n        $res->{running} = PVE::QemuServer::check_running($vmid) ? 1 : 0;\n\n        my ($local_resources, $mapped_resources, $missing_mappings_by_node) =\n            PVE::QemuMigrate::Helpers::check_local_resources($vmconf, $res->{running}, 1);\n\n        my $vga = PVE::QemuServer::parse_vga($vmconf->{vga});\n        if ($res->{running} && $vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {\n            my $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n            push $local_resources->@*, \"clipboard=vnc\"\n                if !PVE::QemuServer::Machine::is_machine_version_at_least($machine_version, 10, 1);\n        }\n\n        $res->{allowed_nodes} = [];\n        $res->{not_allowed_nodes} = {};\n\n        my $storage_nodehash =\n            PVE::QemuServer::check_local_storage_availability($vmconf, $storecfg);\n\n        my $blocking_ha_resources_by_node = {};\n\n        if (PVE::HA::Config::vm_is_ha_managed($vmid)) {\n            (my $dependent_ha_resource, $blocking_ha_resources_by_node) =\n                PVE::HA::Config::get_resource_motion_info(\"vm:$vmid\");\n\n            $res->{'dependent-ha-resources'} = $dependent_ha_resource // [];\n        }\n\n        my $nodelist = PVE::Cluster::get_nodelist();\n        for my $node ($nodelist->@*) {\n            next if $node eq $localnode;\n\n            # extract missing storage info\n            if (my $storage_info = $storage_nodehash->{$node}) {\n                $res->{not_allowed_nodes}->{$node} = $storage_info;\n            }\n\n            # extract missing mappings info\n            my $missing_mappings = $missing_mappings_by_node->{$node};\n            if (scalar($missing_mappings->@*)) {\n                $res->{not_allowed_nodes}->{$node}->{'unavailable-resources'} =\n                    $missing_mappings;\n            }\n\n            # extracting blocking resources for current node\n            if (my $blocking_ha_resources = $blocking_ha_resources_by_node->{$node}) {\n                $res->{not_allowed_nodes}->{$node}->{'blocking-ha-resources'} =\n                    $blocking_ha_resources;\n            }\n\n            # if nothing came up, add it to the allowed nodes\n            if (scalar($res->{not_allowed_nodes}->{$node}->%*) == 0) {\n                push $res->{allowed_nodes}->@*, $node;\n            }\n        }\n\n        my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);\n        $res->{local_disks} = [values %$local_disks];\n\n        $res->{local_resources} = $local_resources;\n        $res->{'mapped-resources'} = [sort keys $mapped_resources->%*];\n        $res->{'mapped-resource-info'} = $mapped_resources;\n        $res->{'has-dbus-vmstate'} = 1;\n\n        return $res;\n\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'migrate_vm',\n    path => '{vmid}/migrate',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Migrate virtual machine. Creates a new migration task.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Migrate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            target => get_standard_option(\n                'pve-node',\n                {\n                    description => \"Target node.\",\n                    completion => \\&PVE::Cluster::complete_migration_target,\n                },\n            ),\n            online => {\n                type => 'boolean',\n                description =>\n                    \"Use online/live migration if VM is running. Ignored if VM is stopped.\",\n                optional => 1,\n            },\n            force => {\n                type => 'boolean',\n                description =>\n                    \"Allow to migrate VMs which use local devices. Only root may use this option.\",\n                optional => 1,\n            },\n            migration_type => {\n                type => 'string',\n                enum => ['secure', 'insecure'],\n                description =>\n                    \"Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.\",\n                optional => 1,\n            },\n            migration_network => {\n                type => 'string',\n                format => 'CIDR',\n                description => \"CIDR of the (sub) network that is used for migration.\",\n                optional => 1,\n            },\n            \"with-local-disks\" => {\n                type => 'boolean',\n                description => \"Enable live storage migration for local disk\",\n                optional => 1,\n            },\n            targetstorage => get_standard_option(\n                'pve-targetstorage',\n                {\n                    completion => \\&PVE::QemuServer::complete_migration_storage,\n                },\n            ),\n            bwlimit => {\n                description => \"Override I/O bandwidth limit (in KiB/s).\",\n                optional => 1,\n                type => 'integer',\n                minimum => '0',\n                default => 'migrate limit from datacenter or storage config',\n            },\n            'with-conntrack-state' => {\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n                description => 'Whether to migrate conntrack entries for running VMs.',\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $target = extract_param($param, 'target');\n\n        my $localnode = PVE::INotify::nodename();\n        raise_param_exc({ target => \"target is local node.\" }) if $target eq $localnode;\n\n        PVE::Cluster::check_cfs_quorum();\n\n        PVE::Cluster::check_node_exists($target);\n\n        my $targetip = PVE::Cluster::remote_node_ip($target);\n\n        my $vmid = extract_param($param, 'vmid');\n\n        raise_param_exc({ force => \"Only root may use this option.\" })\n            if $param->{force} && $authuser ne 'root@pam';\n\n        raise_param_exc({ migration_type => \"Only root may use this option.\" })\n            if $param->{migration_type} && $authuser ne 'root@pam';\n\n        # allow root only until better network permissions are available\n        raise_param_exc({ migration_network => \"Only root may use this option.\" })\n            if $param->{migration_network} && $authuser ne 'root@pam';\n\n        # test if VM exists\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        # try to detect errors early\n\n        PVE::QemuConfig->check_lock($conf);\n\n        if (PVE::QemuServer::check_running($vmid)) {\n            die \"can't migrate running VM without --online\\n\" if !$param->{online};\n\n            my $repl_conf = PVE::ReplicationConfig->new();\n            my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);\n            my $is_replicated_to_target =\n                defined($repl_conf->find_local_replication_job($vmid, $target));\n            if (!$param->{force} && $is_replicated && !$is_replicated_to_target) {\n                die \"Cannot live-migrate replicated VM to node '$target' - not a replication \"\n                    . \"target. Use 'force' to override.\\n\";\n            }\n        } else {\n            warn \"VM isn't running. Doing offline migration instead.\\n\" if $param->{online};\n            $param->{online} = 0;\n            $param->{'with-conntrack-state'} = 0;\n        }\n\n        my $storecfg = PVE::Storage::config();\n        if (my $targetstorage = $param->{targetstorage}) {\n            my $storagemap =\n                eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };\n            raise_param_exc({ targetstorage => \"failed to parse storage map: $@\" })\n                if $@;\n\n            $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])\n                if !defined($storagemap->{identity});\n\n            foreach my $target_sid (values %{ $storagemap->{entries} }) {\n                $check_storage_access_migrate->(\n                    $rpcenv, $authuser, $storecfg, $target_sid, $target,\n                );\n            }\n\n            $check_storage_access_migrate->(\n                $rpcenv, $authuser, $storecfg, $storagemap->{default}, $target,\n            ) if $storagemap->{default};\n\n            PVE::QemuServer::check_storage_availability($storecfg, $conf, $target)\n                if $storagemap->{identity};\n\n            $param->{storagemap} = $storagemap;\n        } else {\n            PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);\n        }\n\n        if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {\n\n            my $hacmd = sub {\n                my $upid = shift;\n\n                print \"Requesting HA migration for VM $vmid to node $target\\n\";\n\n                my $cmd = ['ha-manager', 'migrate', \"vm:$vmid\", $target];\n                run_command($cmd);\n                return;\n            };\n\n            return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);\n\n        } else {\n\n            my $realcmd = sub {\n                PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);\n            };\n\n            my $worker = sub {\n                return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);\n            };\n\n            return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);\n        }\n\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'remote_migrate_vm',\n    path => '{vmid}/remote_migrate',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description =>\n        \"Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Migrate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            'target-vmid' => get_standard_option('pve-vmid', { optional => 1 }),\n            'target-endpoint' => get_standard_option('proxmox-remote', {\n                    description => \"Remote target endpoint\",\n            }),\n            online => {\n                type => 'boolean',\n                description =>\n                    \"Use online/live migration if VM is running. Ignored if VM is stopped.\",\n                optional => 1,\n            },\n            delete => {\n                type => 'boolean',\n                description =>\n                    \"Delete the original VM and related data after successful migration. By default the original VM is kept on the source cluster in a stopped state.\",\n                optional => 1,\n                default => 0,\n            },\n            'target-storage' => get_standard_option(\n                'pve-targetstorage',\n                {\n                    completion => \\&PVE::QemuServer::complete_migration_storage,\n                    optional => 0,\n                },\n            ),\n            'target-bridge' => {\n                type => 'string',\n                description =>\n                    \"Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.\",\n                format => 'bridge-pair-list',\n            },\n            bwlimit => {\n                description => \"Override I/O bandwidth limit (in KiB/s).\",\n                optional => 1,\n                type => 'integer',\n                minimum => '0',\n                default => 'migrate limit from datacenter or storage config',\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $source_vmid = extract_param($param, 'vmid');\n        my $target_endpoint = extract_param($param, 'target-endpoint');\n        my $target_vmid = extract_param($param, 'target-vmid') // $source_vmid;\n\n        my $delete = extract_param($param, 'delete') // 0;\n\n        PVE::Cluster::check_cfs_quorum();\n\n        # test if VM exists\n        my $conf = PVE::QemuConfig->load_config($source_vmid);\n\n        PVE::QemuConfig->check_lock($conf);\n\n        raise_param_exc({ vmid => \"cannot remote-migrate VM that is configured for HA\" })\n            if PVE::HA::Config::service_is_configured(\"vm:$source_vmid\");\n\n        my $remote = PVE::JSONSchema::parse_property_string('proxmox-remote', $target_endpoint);\n\n        # TODO: move this as helper somewhere appropriate?\n        my $conn_args = {\n            protocol => 'https',\n            host => $remote->{host},\n            port => $remote->{port} // 8006,\n            apitoken => $remote->{apitoken},\n        };\n\n        my $fp;\n        if ($fp = $remote->{fingerprint}) {\n            $conn_args->{cached_fingerprints} = { uc($fp) => 1 };\n        }\n\n        print \"Establishing API connection with remote at '$remote->{host}'\\n\";\n\n        my $api_client = PVE::APIClient::LWP->new(%$conn_args);\n\n        if (!defined($fp)) {\n            my $cert_info = $api_client->get(\"/nodes/localhost/certificates/info\");\n            foreach my $cert (@$cert_info) {\n                my $filename = $cert->{filename};\n                next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';\n                $fp = $cert->{fingerprint} if !$fp || $filename eq 'pveproxy-ssl.pem';\n            }\n            $conn_args->{cached_fingerprints} = { uc($fp) => 1 }\n                if defined($fp);\n        }\n\n        my $repl_conf = PVE::ReplicationConfig->new();\n        my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);\n        die \"cannot remote-migrate replicated VM\\n\" if $is_replicated;\n\n        if (PVE::QemuServer::check_running($source_vmid)) {\n            die \"can't migrate running VM without --online\\n\" if !$param->{online};\n\n        } else {\n            warn \"VM isn't running. Doing offline migration instead.\\n\" if $param->{online};\n            $param->{online} = 0;\n        }\n\n        my $storecfg = PVE::Storage::config();\n        my $target_storage = extract_param($param, 'target-storage');\n        my $storagemap =\n            eval { PVE::JSONSchema::parse_idmap($target_storage, 'pve-storage-id') };\n        raise_param_exc({ 'target-storage' => \"failed to parse storage map: $@\" })\n            if $@;\n\n        my $target_bridge = extract_param($param, 'target-bridge');\n        my $bridgemap = eval { PVE::JSONSchema::parse_idmap($target_bridge, 'pve-bridge-id') };\n        raise_param_exc({ 'target-bridge' => \"failed to parse bridge map: $@\" })\n            if $@;\n\n        die \"remote migration requires explicit storage mapping!\\n\"\n            if $storagemap->{identity};\n\n        $param->{storagemap} = $storagemap;\n        $param->{bridgemap} = $bridgemap;\n        $param->{remote} = {\n            conn => $conn_args, # re-use fingerprint for tunnel\n            client => $api_client,\n            vmid => $target_vmid,\n        };\n        $param->{migration_type} = 'websocket';\n        $param->{'with-local-disks'} = 1;\n        $param->{delete} = $delete if $delete;\n\n        my $cluster_status = $api_client->get(\"/cluster/status\");\n        my $target_node;\n        foreach my $entry (@$cluster_status) {\n            next if $entry->{type} ne 'node';\n            if ($entry->{local}) {\n                $target_node = $entry->{name};\n                last;\n            }\n        }\n\n        die \"couldn't determine endpoint's node name\\n\"\n            if !defined($target_node);\n\n        my $realcmd = sub {\n            PVE::QemuMigrate->migrate($target_node, $remote->{host}, $source_vmid, $param);\n        };\n\n        my $worker = sub {\n            return PVE::GuestHelpers::guest_migration_lock($source_vmid, 10, $realcmd);\n        };\n\n        return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'monitor',\n    path => '{vmid}/monitor',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Execute QEMU monitor commands.\",\n    permissions => {\n        description => PVE::API2::Qemu::HMPPerms::generate_description(),\n        check => ['perm', '/vms/{vmid}', ['Sys.Audit', 'Sys.Modify'], any => 1],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            command => {\n                type => 'string',\n                description => \"The monitor command.\",\n            },\n        },\n    },\n    returns => { type => 'string' },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $command = $param->{command} or die \"no command specified\\n\";\n        die \"unexpected command '$command'\\n\" if $command !~ m/^\\s*(\\S+)/;\n        my $command_name = $1;\n        my $required_perm = $PVE::API2::Qemu::HMPPerms::hmp_command_perms->{$command_name};\n        if (!$required_perm) {\n            my $msg =\n                \"command '$command_name' non-existent or not assigned a required permission\"\n                . \" yet, limiting to root user\\n\";\n            die $msg if $authuser ne 'root@pam';\n            warn $msg;\n            $required_perm = 'root';\n        }\n\n        if ($required_perm eq 'root') {\n            die \"root-only command '$command_name'\\n\" if $authuser ne 'root@pam';\n        } elsif ($required_perm eq 'Sys.Modify') {\n            $rpcenv->check_full($authuser, \"/\", ['Sys.Modify']);\n        } elsif ($required_perm eq 'none') {\n            # nothing to check\n        } else {\n            die \"unexpected required permission '$required_perm' for command '$command_name'\\n\";\n        }\n\n        my $vmid = $param->{vmid};\n\n        my $conf = PVE::QemuConfig->load_config($vmid); # check if VM exists\n\n        my $res = '';\n        eval { $res = PVE::QemuServer::Monitor::hmp_cmd($vmid, $param->{command}, 25); };\n        $res = \"ERROR: $@\" if $@;\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'resize_vm',\n    path => '{vmid}/resize',\n    method => 'PUT',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Extend volume size.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Config.Disk']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            skiplock => get_standard_option('skiplock'),\n            disk => {\n                type => 'string',\n                description => \"The disk you want to resize.\",\n                enum => [PVE::QemuServer::Drive::valid_drive_names()],\n            },\n            size => {\n                type => 'string',\n                pattern => '\\+?\\d+(\\.\\d+)?[KMGT]?',\n                description =>\n                    \"The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.\",\n            },\n            digest => {\n                type => 'string',\n                description =>\n                    'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',\n                maxLength => 40,\n                optional => 1,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $digest = extract_param($param, 'digest');\n\n        my $disk = extract_param($param, 'disk');\n\n        my $sizestr = extract_param($param, 'size');\n\n        my $skiplock = extract_param($param, 'skiplock');\n        raise_param_exc({ skiplock => \"Only root may use this option.\" })\n            if $skiplock && $authuser ne 'root@pam';\n\n        my $storecfg = PVE::Storage::config();\n\n        my $updatefn = sub {\n\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            die \"checksum mismatch (file change by other user?)\\n\"\n                if $digest && $digest ne $conf->{digest};\n            PVE::QemuConfig->check_lock($conf) if !$skiplock;\n\n            die \"disk '$disk' does not exist\\n\" if !$conf->{$disk};\n\n            my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});\n\n            my (undef, undef, undef, undef, undef, undef, $format) =\n                PVE::Storage::parse_volname($storecfg, $drive->{file});\n\n            my $volid = $drive->{file};\n\n            die \"disk '$disk' has no associated volume\\n\" if !$volid;\n\n            die \"you can't resize a cdrom\\n\" if PVE::QemuServer::drive_is_cdrom($drive);\n\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n            $rpcenv->check($authuser, \"/storage/$storeid\", ['Datastore.AllocateSpace']);\n\n            PVE::Storage::activate_volumes($storecfg, [$volid]);\n            my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);\n\n            die \"Could not determine current size of volume '$volid'\\n\" if !defined($size);\n\n            die \"internal error\" if $sizestr !~ m/^(\\+)?(\\d+(\\.\\d+)?)([KMGT])?$/;\n            my ($ext, $newsize, $unit) = ($1, $2, $4);\n            if ($unit) {\n                if ($unit eq 'K') {\n                    $newsize = $newsize * 1024;\n                } elsif ($unit eq 'M') {\n                    $newsize = $newsize * 1024 * 1024;\n                } elsif ($unit eq 'G') {\n                    $newsize = $newsize * 1024 * 1024 * 1024;\n                } elsif ($unit eq 'T') {\n                    $newsize = $newsize * 1024 * 1024 * 1024 * 1024;\n                }\n            }\n            $newsize += $size if $ext;\n            $newsize = int($newsize);\n\n            die \"shrinking disks is not supported\\n\" if $newsize < $size;\n\n            return if $size == $newsize;\n\n            PVE::Cluster::log_msg(\n                'info',\n                $authuser,\n                \"update VM $vmid: resize --disk $disk --size $sizestr\",\n            );\n\n            PVE::QemuServer::Blockdev::resize(\n                $vmid, \"drive-$disk\", $storecfg, $volid, $newsize,\n            );\n\n            $drive->{size} = $newsize;\n            $conf->{$disk} = PVE::QemuServer::print_drive($drive);\n\n            PVE::QemuConfig->write_config($vmid, $conf);\n        };\n\n        my $worker = sub {\n            PVE::QemuConfig->lock_config($vmid, $updatefn);\n        };\n\n        return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'snapshot_list',\n    path => '{vmid}/snapshot',\n    method => 'GET',\n    description => \"List all snapshots.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    proxyto => 'node',\n    protected => 1, # qemu pid files are only readable by root\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            node => get_standard_option('pve-node'),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => \"object\",\n            properties => {\n                name => {\n                    description =>\n                        \"Snapshot identifier. Value 'current' identifies the current VM.\",\n                    type => 'string',\n                },\n                vmstate => {\n                    description => \"Snapshot includes RAM.\",\n                    type => 'boolean',\n                    optional => 1,\n                },\n                description => {\n                    description => \"Snapshot description.\",\n                    type => 'string',\n                },\n                snaptime => {\n                    description => \"Snapshot creation time\",\n                    type => 'integer',\n                    renderer => 'timestamp',\n                    optional => 1,\n                },\n                parent => {\n                    description => \"Parent snapshot identifier.\",\n                    type => 'string',\n                    optional => 1,\n                },\n            },\n        },\n        links => [{ rel => 'child', href => \"{name}\" }],\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n        my $snaphash = $conf->{snapshots} || {};\n\n        my $res = [];\n\n        foreach my $name (keys %$snaphash) {\n            my $d = $snaphash->{$name};\n            my $item = {\n                name => $name,\n                snaptime => $d->{snaptime} || 0,\n                vmstate => $d->{vmstate} ? 1 : 0,\n                description => $d->{description} || '',\n            };\n            $item->{parent} = $d->{parent} if $d->{parent};\n            $item->{snapstate} = $d->{snapstate} if $d->{snapstate};\n            push @$res, $item;\n        }\n\n        my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;\n        my $current = {\n            name => 'current',\n            digest => $conf->{digest},\n            running => $running,\n            description => \"You are here!\",\n        };\n        $current->{parent} = $conf->{parent} if $conf->{parent};\n\n        push @$res, $current;\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'snapshot',\n    path => '{vmid}/snapshot',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Snapshot a VM.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Snapshot']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            snapname => get_standard_option('pve-snapshot-name'),\n            vmstate => {\n                optional => 1,\n                type => 'boolean',\n                description => \"Save the vmstate\",\n            },\n            description => {\n                optional => 1,\n                type => 'string',\n                description => \"A textual description or comment.\",\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $snapname = extract_param($param, 'snapname');\n\n        die \"unable to use snapshot name 'current' (reserved name)\\n\"\n            if $snapname eq 'current';\n\n        die \"unable to use snapshot name 'pending' (reserved name)\\n\"\n            if lc($snapname) eq 'pending';\n\n        my $vmconf = PVE::QemuConfig->load_config($vmid);\n        PVE::QemuMigrate::Helpers::check_non_migratable_resources($vmconf, $param->{vmstate},\n            0);\n\n        my $realcmd = sub {\n            PVE::Cluster::log_msg('info', $authuser, \"snapshot VM $vmid: $snapname\");\n            PVE::QemuConfig->snapshot_create(\n                $vmid, $snapname, $param->{vmstate}, $param->{description},\n            );\n        };\n\n        return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'snapshot_cmd_idx',\n    path => '{vmid}/snapshot/{snapname}',\n    description => '',\n    method => 'GET',\n    permissions => {\n        user => 'all',\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option('pve-vmid'),\n            node => get_standard_option('pve-node'),\n            snapname => get_standard_option('pve-snapshot-name'),\n        },\n    },\n    returns => {\n        type => 'array',\n        items => {\n            type => \"object\",\n            properties => {},\n        },\n        links => [{ rel => 'child', href => \"{cmd}\" }],\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $res = [];\n\n        push @$res, { cmd => 'rollback' };\n        push @$res, { cmd => 'config' };\n\n        return $res;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'update_snapshot_config',\n    path => '{vmid}/snapshot/{snapname}/config',\n    method => 'PUT',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Update snapshot metadata.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Snapshot']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            snapname => get_standard_option('pve-snapshot-name'),\n            description => {\n                optional => 1,\n                type => 'string',\n                description => \"A textual description or comment.\",\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $snapname = extract_param($param, 'snapname');\n\n        return if !defined($param->{description});\n\n        my $updatefn = sub {\n\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            PVE::QemuConfig->check_lock($conf);\n\n            my $snap = $conf->{snapshots}->{$snapname};\n\n            die \"snapshot '$snapname' does not exist\\n\" if !defined($snap);\n\n            $snap->{description} = $param->{description} if defined($param->{description});\n\n            PVE::QemuConfig->write_config($vmid, $conf);\n        };\n\n        PVE::QemuConfig->lock_config($vmid, $updatefn);\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'get_snapshot_config',\n    path => '{vmid}/snapshot/{snapname}/config',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Get snapshot configuration\",\n    permissions => {\n        check =>\n            ['perm', '/vms/{vmid}', ['VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit'], any => 1],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            snapname => get_standard_option('pve-snapshot-name'),\n        },\n    },\n    returns => { type => \"object\" },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $snapname = extract_param($param, 'snapname');\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $snap = $conf->{snapshots}->{$snapname};\n\n        die \"snapshot '$snapname' does not exist\\n\" if !defined($snap);\n\n        return $snap;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'rollback',\n    path => '{vmid}/snapshot/{snapname}/rollback',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Rollback VM state to specified snapshot.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Snapshot', 'VM.Snapshot.Rollback'], any => 1],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            snapname => get_standard_option('pve-snapshot-name'),\n            start => {\n                type => 'boolean',\n                description =>\n                    \"Whether the VM should get started after rolling back successfully.\"\n                    . \" (Note: VMs will be automatically started if the snapshot includes RAM.)\",\n                optional => 1,\n                default => 0,\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $snapname = extract_param($param, 'snapname');\n\n        my $realcmd = sub {\n            PVE::Cluster::log_msg('info', $authuser, \"rollback snapshot VM $vmid: $snapname\");\n            PVE::QemuConfig->snapshot_rollback($vmid, $snapname);\n\n            if ($param->{start} && !PVE::QemuServer::Helpers::vm_running_locally($vmid)) {\n                PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node });\n            }\n        };\n\n        my $worker = sub {\n            # hold migration lock, this makes sure that nobody create replication snapshots\n            return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);\n        };\n\n        return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'delsnapshot',\n    path => '{vmid}/snapshot/{snapname}',\n    method => 'DELETE',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Delete a VM snapshot.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Snapshot']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            snapname => get_standard_option('pve-snapshot-name'),\n            force => {\n                optional => 1,\n                type => 'boolean',\n                description =>\n                    \"For removal from config file, even if removing disk snapshots fails.\",\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $snapname = extract_param($param, 'snapname');\n\n        my $lock_obtained;\n        my $do_delete = sub {\n            $lock_obtained = 1;\n\n            PVE::QemuConfig->lock_config(\n                $vmid,\n                sub {\n                    my $conf = PVE::QemuConfig->load_config($vmid);\n                    assert_tpm_snapshot_delete_possible(\n                        $vmid, $conf, $conf->{snapshots}->{$snapname}, $snapname,\n                    );\n                },\n            );\n\n            PVE::Cluster::log_msg('info', $authuser, \"delete snapshot VM $vmid: $snapname\");\n            PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});\n        };\n\n        my $realcmd = sub {\n            if ($param->{force}) {\n                $do_delete->();\n            } else {\n                eval { PVE::GuestHelpers::guest_migration_lock($vmid, 10, $do_delete); };\n                if (my $err = $@) {\n                    die $err if $lock_obtained;\n                    die \"Failed to obtain guest migration lock - replication running?\\n\";\n                }\n            }\n        };\n\n        return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'template',\n    path => '{vmid}/template',\n    method => 'POST',\n    protected => 1,\n    proxyto => 'node',\n    description => \"Create a Template.\",\n    permissions => {\n        description => \"You need 'VM.Allocate' permissions on /vms/{vmid}\",\n        check => ['perm', '/vms/{vmid}', ['VM.Allocate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_stopped },\n            ),\n            disk => {\n                optional => 1,\n                type => 'string',\n                description => \"If you want to convert only 1 disk to base image.\",\n                enum => [PVE::QemuServer::Drive::valid_drive_names()],\n            },\n\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $disk = extract_param($param, 'disk');\n\n        my $load_and_check = sub {\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            PVE::QemuConfig->check_lock($conf);\n\n            die \"unable to create template, because VM contains snapshots\\n\"\n                if $conf->{snapshots} && scalar(keys %{ $conf->{snapshots} });\n\n            die \"you can't convert a template to a template\\n\"\n                if PVE::QemuConfig->is_template($conf) && !$disk;\n\n            die \"you can't convert a VM to template if VM is running\\n\"\n                if PVE::QemuServer::check_running($vmid);\n\n            return $conf;\n        };\n\n        $load_and_check->();\n\n        my $realcmd = sub {\n            PVE::QemuConfig->lock_config(\n                $vmid,\n                sub {\n                    my $conf = $load_and_check->();\n\n                    $conf->{template} = 1;\n                    PVE::QemuConfig->write_config($vmid, $conf);\n\n                    PVE::QemuServer::template_create($vmid, $conf, $disk);\n                },\n            );\n        };\n\n        return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'cloudinit_generated_config_dump',\n    path => '{vmid}/cloudinit/dump',\n    method => 'GET',\n    proxyto => 'node',\n    description => \"Get automatically generated cloudinit config.\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Audit']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            type => {\n                description => 'Config type.',\n                type => 'string',\n                enum => ['user', 'network', 'meta'],\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $conf = PVE::QemuConfig->load_config($param->{vmid});\n\n        my $mask_password =\n            !$rpcenv->check($authuser, '/vms/{vmid}', ['VM.Config.Cloudinit'], 1);\n\n        return PVE::QemuServer::Cloudinit::dump_cloudinit_config(\n            $conf,\n            $param->{vmid},\n            $param->{type},\n            $mask_password,\n        );\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'mtunnel',\n    path => '{vmid}/mtunnel',\n    method => 'POST',\n    protected => 1,\n    description => 'Migration tunnel endpoint - only for internal use by VM migration.',\n    permissions => {\n        check => [\n            'and', ['perm', '/vms/{vmid}', ['VM.Allocate']], ['perm', '/', ['Sys.Incoming']],\n        ],\n        description => \"You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming\"\n            . \" on '/'. Further permission checks happen during the actual migration.\",\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            storages => {\n                type => 'string',\n                format => 'pve-storage-id-list',\n                optional => 1,\n                description =>\n                    'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',\n            },\n            bridges => {\n                type => 'string',\n                format => 'pve-bridge-id-list',\n                optional => 1,\n                description =>\n                    'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',\n            },\n        },\n    },\n    returns => {\n        additionalProperties => 0,\n        properties => {\n            upid => { type => 'string' },\n            ticket => { type => 'string' },\n            socket => { type => 'string' },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $node = extract_param($param, 'node');\n        my $vmid = extract_param($param, 'vmid');\n\n        my $storages = extract_param($param, 'storages');\n        my $bridges = extract_param($param, 'bridges');\n\n        my $nodename = PVE::INotify::nodename();\n\n        raise_param_exc({\n            node => \"node needs to be 'localhost' or local hostname '$nodename'\" })\n            if $node ne 'localhost' && $node ne $nodename;\n\n        $node = $nodename;\n\n        my $storecfg = PVE::Storage::config();\n        foreach my $storeid (PVE::Tools::split_list($storages)) {\n            $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);\n        }\n\n        foreach my $bridge (PVE::Tools::split_list($bridges)) {\n            PVE::Network::read_bridge_mtu($bridge);\n        }\n\n        PVE::Cluster::check_cfs_quorum();\n\n        my $lock = 'create';\n        eval { PVE::QemuConfig->create_and_lock_config($vmid, 0, $lock); };\n\n        raise_param_exc({ vmid => \"unable to create empty VM config - $@\" })\n            if $@;\n\n        my $realcmd = sub {\n            my $state = {\n                storecfg => PVE::Storage::config(),\n                lock => $lock,\n                vmid => $vmid,\n            };\n\n            my $run_locked = sub {\n                my ($code, $params) = @_;\n                return PVE::QemuConfig->lock_config(\n                    $state->{vmid},\n                    sub {\n                        my $conf = PVE::QemuConfig->load_config($state->{vmid});\n\n                        $state->{conf} = $conf;\n\n                        die \"Encountered wrong lock - aborting mtunnel command handling.\\n\"\n                            if $state->{lock} && !PVE::QemuConfig->has_lock($conf, $state->{lock});\n\n                        return $code->($params);\n                    },\n                );\n            };\n\n            my $cmd_desc = {\n                config => {\n                    conf => {\n                        type => 'string',\n                        description => 'Full VM config, adapted for target cluster/node',\n                    },\n                    'firewall-config' => {\n                        type => 'string',\n                        description => 'VM firewall config',\n                        optional => 1,\n                    },\n                },\n                disk => {\n                    format => PVE::JSONSchema::get_standard_option('pve-qm-image-format'),\n                    storage => {\n                        type => 'string',\n                        format => 'pve-storage-id',\n                    },\n                    drive => {\n                        type => 'object',\n                        description => 'parsed drive information without volid and format',\n                    },\n                },\n                start => {\n                    start_params => {\n                        type => 'object',\n                        description => 'params passed to vm_start_nolock',\n                    },\n                    migrate_opts => {\n                        type => 'object',\n                        description => 'migrate_opts passed to vm_start_nolock',\n                    },\n                },\n                ticket => {\n                    path => {\n                        type => 'string',\n                        description =>\n                            'socket path for which the ticket should be valid. must be known to current mtunnel instance.',\n                    },\n                },\n                quit => {\n                    cleanup => {\n                        type => 'boolean',\n                        description => 'remove VM config and disks, aborting migration',\n                        default => 0,\n                    },\n                },\n                'disk-import' => $PVE::StorageTunnel::cmd_schema->{'disk-import'},\n                'query-disk-import' => $PVE::StorageTunnel::cmd_schema->{'query-disk-import'},\n                bwlimit => $PVE::StorageTunnel::cmd_schema->{bwlimit},\n            };\n\n            my $cmd_handlers = {\n                'version' => sub {\n                    # compared against other end's version\n                    # bump/reset for breaking changes\n                    # bump/bump for opt-in changes\n                    return {\n                        api => $PVE::QemuMigrate::WS_TUNNEL_VERSION,\n                        age => 0,\n                    };\n                },\n                'config' => sub {\n                    my ($params) = @_;\n\n                    # parse and write out VM FW config if given\n                    if (my $fw_conf = $params->{'firewall-config'}) {\n                        my ($path, $fh) = PVE::Tools::tempfile_contents($fw_conf, 700);\n\n                        my $empty_conf = {\n                            rules => [],\n                            options => {},\n                            aliases => {},\n                            ipset => {},\n                            ipset_comments => {},\n                        };\n                        my $cluster_fw_conf = PVE::Firewall::load_clusterfw_conf();\n\n                        # TODO: add flag for strict parsing?\n                        # TODO: add import sub that does all this given raw content?\n                        my $vmfw_conf = PVE::Firewall::generic_fw_config_parser(\n                            $path, $cluster_fw_conf, $empty_conf, 'vm',\n                        );\n                        $vmfw_conf->{vmid} = $state->{vmid};\n                        PVE::Firewall::save_vmfw_conf($state->{vmid}, $vmfw_conf);\n\n                        $state->{cleanup}->{fw} = 1;\n                    }\n\n                    my $conf_fn = \"incoming/qemu-server/$state->{vmid}.conf\";\n                    my $new_conf =\n                        PVE::QemuServer::parse_vm_config($conf_fn, $params->{conf}, 1);\n                    delete $new_conf->{lock};\n                    delete $new_conf->{digest};\n\n                    # TODO handle properly?\n                    delete $new_conf->{snapshots};\n                    delete $new_conf->{parent};\n                    delete $new_conf->{pending};\n\n                    # not handled by update_vm_api\n                    my $vmgenid = delete $new_conf->{vmgenid};\n                    my $meta = delete $new_conf->{meta};\n\n                    my $special_sections = delete $new_conf->{'special-sections'} // {};\n\n                    # fleecing state is specific to source side\n                    delete $special_sections->{fleecing};\n\n                    $new_conf->{skip_cloud_init} = 1; # re-use image from source side\n\n                    # TODO PVE 10 - remove backwards-compat handling?\n                    my $cloudinit = delete $new_conf->{cloudinit};\n                    if ($cloudinit) {\n                        if ($special_sections->{cloudinit}) {\n                            warn \"config has duplicate special 'cloudinit' sections - skipping\"\n                                . \" legacy variant\\n\";\n                        } else {\n                            $special_sections->{cloudinit} = $cloudinit;\n                        }\n                    }\n\n                    $new_conf->{vmid} = $state->{vmid};\n                    $new_conf->{node} = $node;\n\n                    PVE::QemuConfig->remove_lock($state->{vmid}, 'create');\n\n                    eval { $update_vm_api->($new_conf, 1); };\n                    if (my $err = $@) {\n                        # revert to locked previous config\n                        my $conf = PVE::QemuConfig->load_config($state->{vmid});\n                        $conf->{lock} = 'create';\n                        PVE::QemuConfig->write_config($state->{vmid}, $conf);\n\n                        die $err;\n                    }\n\n                    my $conf = PVE::QemuConfig->load_config($state->{vmid});\n                    $conf->{lock} = 'migrate';\n                    $conf->{vmgenid} = $vmgenid if defined($vmgenid);\n                    $conf->{meta} = $meta if defined($meta);\n                    $conf->{'special-sections'} = $special_sections;\n                    PVE::QemuConfig->write_config($state->{vmid}, $conf);\n\n                    $state->{lock} = 'migrate';\n\n                    return;\n                },\n                'bwlimit' => sub {\n                    my ($params) = @_;\n                    return PVE::StorageTunnel::handle_bwlimit($params);\n                },\n                'disk' => sub {\n                    my ($params) = @_;\n\n                    my $format = $params->{format};\n                    my $storeid = $params->{storage};\n                    my $drive = $params->{drive};\n\n                    $check_storage_access_migrate->(\n                        $rpcenv, $authuser, $state->{storecfg}, $storeid, $node,\n                    );\n\n                    my $storagemap = {\n                        default => $storeid,\n                    };\n\n                    my $source_volumes = {\n                        'disk' => [\n                            undef, $storeid, $drive, 0, $format,\n                        ],\n                    };\n\n                    my $res = PVE::QemuServer::vm_migrate_alloc_nbd_disks(\n                        $state->{storecfg}, $state->{vmid}, $source_volumes, $storagemap,\n                    );\n                    if (defined($res->{disk})) {\n                        $state->{cleanup}->{volumes}->{ $res->{disk}->{volid} } = 1;\n                        return $res->{disk};\n                    } else {\n                        die \"failed to allocate NBD disk..\\n\";\n                    }\n                },\n                'disk-import' => sub {\n                    my ($params) = @_;\n\n                    $check_storage_access_migrate->(\n                        $rpcenv, $authuser, $state->{storecfg}, $params->{storage}, $node,\n                    );\n\n                    $params->{unix} = \"/run/qemu-server/$state->{vmid}.storage\";\n\n                    return PVE::StorageTunnel::handle_disk_import($state, $params);\n                },\n                'query-disk-import' => sub {\n                    my ($params) = @_;\n\n                    return PVE::StorageTunnel::handle_query_disk_import($state, $params);\n                },\n                'start' => sub {\n                    my ($params) = @_;\n\n                    my $info = PVE::QemuServer::vm_start_nolock(\n                        $state->{storecfg},\n                        $state->{vmid},\n                        $state->{conf},\n                        $params->{start_params},\n                        $params->{migrate_opts},\n                    );\n\n                    if ($info->{migrate}->{proto} ne 'unix') {\n                        PVE::QemuServer::vm_stop(undef, $state->{vmid}, 1, 1);\n                        die \"migration over non-UNIX sockets not possible\\n\";\n                    }\n\n                    my $socket = $info->{migrate}->{addr};\n                    chown $state->{socket_uid}, -1, $socket;\n                    $state->{sockets}->{$socket} = 1;\n\n                    my $unix_sockets = $info->{migrate}->{unix_sockets};\n                    foreach my $socket (@$unix_sockets) {\n                        chown $state->{socket_uid}, -1, $socket;\n                        $state->{sockets}->{$socket} = 1;\n                    }\n                    return $info;\n                },\n                'fstrim' => sub {\n                    if (PVE::QemuServer::Agent::qga_check_running($state->{vmid})) {\n                        eval { mon_cmd($state->{vmid}, \"guest-fstrim\") };\n                        warn \"fstrim failed: $@\\n\" if $@;\n                    }\n                    return;\n                },\n                'stop' => sub {\n                    PVE::QemuServer::vm_stop(undef, $state->{vmid}, 1, 1);\n                    return;\n                },\n                'nbdstop' => sub {\n                    PVE::QemuServer::QMPHelpers::nbd_stop($state->{vmid});\n                    return;\n                },\n                'resume' => sub {\n                    if (PVE::QemuServer::Helpers::vm_running_locally($state->{vmid})) {\n                        PVE::QemuServer::RunState::vm_resume($state->{vmid}, 1, 1);\n                    } else {\n                        die \"VM $state->{vmid} not running\\n\";\n                    }\n                    return;\n                },\n                'unlock' => sub {\n                    PVE::QemuConfig->remove_lock($state->{vmid}, $state->{lock});\n                    delete $state->{lock};\n                    return;\n                },\n                'ticket' => sub {\n                    my ($params) = @_;\n\n                    my $path = $params->{path};\n\n                    die \"Not allowed to generate ticket for unknown socket '$path'\\n\"\n                        if !defined($state->{sockets}->{$path});\n\n                    return {\n                        ticket =>\n                            PVE::AccessControl::assemble_tunnel_ticket($authuser, \"/socket/$path\"),\n                    };\n                },\n                'quit' => sub {\n                    my ($params) = @_;\n\n                    if ($params->{cleanup}) {\n                        if ($state->{cleanup}->{fw}) {\n                            PVE::Firewall::remove_vmfw_conf($state->{vmid});\n                        }\n\n                        for my $volid (keys $state->{cleanup}->{volumes}->%*) {\n                            print \"freeing volume '$volid' as part of cleanup\\n\";\n                            eval { PVE::Storage::vdisk_free($state->{storecfg}, $volid) };\n                            warn $@ if $@;\n                        }\n\n                        PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($state->{vmid});\n                        PVE::QemuServer::destroy_vm($state->{storecfg}, $state->{vmid}, 1);\n                    }\n\n                    print \"switching to exit-mode, waiting for client to disconnect\\n\";\n                    $state->{exit} = 1;\n                    return;\n                },\n            };\n\n            $run_locked->(sub {\n                my $socket_addr = \"/run/qemu-server/$state->{vmid}.mtunnel\";\n                unlink $socket_addr;\n\n                $state->{socket} = IO::Socket::UNIX->new(\n                    Type => SOCK_STREAM(),\n                    Local => $socket_addr,\n                    Listen => 1,\n                );\n\n                $state->{socket_uid} = getpwnam('www-data')\n                    or die \"Failed to resolve user 'www-data' to numeric UID\\n\";\n                chown $state->{socket_uid}, -1, $socket_addr;\n            });\n\n            print \"mtunnel started\\n\";\n\n            my $conn = eval {\n                PVE::Tools::run_with_timeout(300, sub { $state->{socket}->accept() });\n            };\n            if ($@) {\n                warn \"Failed to accept tunnel connection - $@\\n\";\n\n                warn \"Removing tunnel socket..\\n\";\n                unlink $state->{socket};\n\n                warn \"Removing temporary VM config..\\n\";\n                $run_locked->(sub {\n                    PVE::QemuServer::destroy_vm($state->{storecfg}, $state->{vmid}, 1);\n                });\n\n                die \"Exiting mtunnel\\n\";\n            }\n\n            $state->{conn} = $conn;\n\n            my $reply_err = sub {\n                my ($msg) = @_;\n\n                my $reply = JSON::encode_json({\n                    success => JSON::false,\n                    msg => $msg,\n                });\n                $conn->print(\"$reply\\n\");\n                $conn->flush();\n            };\n\n            my $reply_ok = sub {\n                my ($res) = @_;\n\n                $res->{success} = JSON::true;\n                my $reply = JSON::encode_json($res);\n                $conn->print(\"$reply\\n\");\n                $conn->flush();\n            };\n\n            while (my $line = <$conn>) {\n                chomp $line;\n\n                # untaint, we validate below if needed\n                ($line) = $line =~ /^(.*)$/;\n                my $parsed = eval { JSON::decode_json($line) };\n                if ($@) {\n                    $reply_err->(\"failed to parse command - $@\");\n                    next;\n                }\n\n                my $cmd = delete $parsed->{cmd};\n                if (!defined($cmd)) {\n                    $reply_err->(\"'cmd' missing\");\n                } elsif ($state->{exit}) {\n                    $reply_err->(\"tunnel is in exit-mode, processing '$cmd' cmd not possible\");\n                    next;\n                } elsif (my $handler = $cmd_handlers->{$cmd}) {\n                    print \"received command '$cmd'\\n\";\n                    eval {\n                        if (my $props = $cmd_desc->{$cmd}) {\n                            my $schema = {\n                                type => 'object',\n                                properties => $props,\n                            };\n                            PVE::JSONSchema::validate($parsed, $schema);\n                        } else {\n                            $parsed = {};\n                        }\n                        my $res = $run_locked->($handler, $parsed);\n                        $reply_ok->($res);\n                    };\n                    $reply_err->(\"failed to handle '$cmd' command - $@\")\n                        if $@;\n                } else {\n                    $reply_err->(\"unknown command '$cmd' given\");\n                }\n            }\n\n            if ($state->{exit}) {\n                print \"mtunnel exited\\n\";\n            } else {\n                die \"mtunnel exited unexpectedly\\n\";\n            }\n        };\n\n        my $socket_addr = \"/run/qemu-server/$vmid.mtunnel\";\n        my $ticket =\n            PVE::AccessControl::assemble_tunnel_ticket($authuser, \"/socket/$socket_addr\");\n        my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);\n\n        return {\n            ticket => $ticket,\n            upid => $upid,\n            socket => $socket_addr,\n        };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'mtunnelwebsocket',\n    path => '{vmid}/mtunnelwebsocket',\n    method => 'GET',\n    permissions => {\n        description =>\n            \"You need to pass a ticket valid for the selected socket. Tickets can be created via the mtunnel API call, which will check permissions accordingly.\",\n        user => 'all', # check inside\n    },\n    description =>\n        'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option('pve-vmid'),\n            socket => {\n                type => \"string\",\n                description => \"unix socket to forward to\",\n            },\n            ticket => {\n                type => \"string\",\n                description =>\n                    \"ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command\",\n            },\n        },\n    },\n    returns => {\n        type => \"object\",\n        properties => {\n            port => { type => 'string', optional => 1 },\n            socket => { type => 'string', optional => 1 },\n        },\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $nodename = PVE::INotify::nodename();\n        my $node = extract_param($param, 'node');\n\n        raise_param_exc({\n            node => \"node needs to be 'localhost' or local hostname '$nodename'\" })\n            if $node ne 'localhost' && $node ne $nodename;\n\n        my $vmid = $param->{vmid};\n        # check VM exists\n        PVE::QemuConfig->load_config($vmid);\n\n        my $socket = $param->{socket};\n        PVE::AccessControl::verify_tunnel_ticket($param->{ticket}, $authuser,\n            \"/socket/$socket\");\n\n        return { socket => $socket };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'dbus_vmstate',\n    path => '{vmid}/dbus-vmstate',\n    method => 'POST',\n    proxyto => 'node',\n    description => 'Control the dbus-vmstate helper for a given running VM.',\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Migrate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            action => {\n                type => 'string',\n                enum => [qw(start stop)],\n                description => 'Action to perform on the DBus VMState helper.',\n                optional => 0,\n            },\n        },\n    },\n    returns => {\n        type => 'null',\n    },\n    code => sub {\n        my ($param) = @_;\n        my ($node, $vmid, $action) = $param->@{qw(node vmid action)};\n\n        my $nodename = PVE::INotify::nodename();\n        if ($node ne 'localhost' && $node ne $nodename) {\n            raise_param_exc(\n                { node => \"node needs to be 'localhost' or local hostname '$nodename'\" });\n        }\n\n        if (!PVE::QemuServer::Helpers::vm_running_locally($vmid)) {\n            raise_param_exc({ node => \"VM $vmid not running locally on node '$nodename'\" });\n        }\n\n        if ($action eq 'start') {\n            syslog('info', \"starting dbus-vmstate helper for VM $vmid\\n\");\n            PVE::QemuServer::DBusVMState::qemu_add_dbus_vmstate($vmid);\n        } elsif ($action eq 'stop') {\n            syslog('info', \"stopping dbus-vmstate helper for VM $vmid\\n\");\n            PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($vmid);\n        } else {\n            die \"unknown action $action\\n\";\n        }\n    },\n});\n\n1;\n"
  },
  {
    "path": "src/PVE/CLI/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES=qm.pm qmrestore.pm\n\n.PHONY: install\ninstall: $(SOURCES)\n\tinstall -d -m 0755 $(DESTDIR)$(PERLDIR)/PVE/CLI\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/CLI/$$i; done\n\n\n"
  },
  {
    "path": "src/PVE/CLI/qm.pm",
    "content": "package PVE::CLI::qm;\n\nuse strict;\nuse warnings;\n\n# Note: disable '+' prefix for Getopt::Long (for resize command)\nuse Getopt::Long qw(:config no_getopt_compat);\n\nuse Fcntl ':flock';\nuse File::Path;\nuse IO::Select;\nuse IO::Socket::UNIX;\nuse JSON;\nuse POSIX qw(strftime);\nuse Term::ReadLine;\nuse URI::Escape;\n\nuse PVE::APIClient::LWP;\nuse PVE::Cluster;\nuse PVE::Exception qw(raise_param_exc);\nuse PVE::GuestHelpers;\nuse PVE::GuestImport::OVF;\nuse PVE::INotify;\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::Network;\nuse PVE::RPCEnvironment;\nuse PVE::SafeSyslog;\nuse PVE::Tools qw(extract_param file_get_contents);\n\nuse PVE::API2::Qemu::Agent;\nuse PVE::API2::Qemu;\nuse PVE::QemuConfig;\nuse PVE::QemuServer::Drive qw(is_valid_drivename parse_drive print_drive);\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Agent;\nuse PVE::QemuServer::ImportDisk;\nuse PVE::QemuServer::Monitor qw(mon_cmd);\nuse PVE::QemuServer::OVMF;\nuse PVE::QemuServer::QMPHelpers;\nuse PVE::QemuServer::RunState;\nuse PVE::QemuServer::DBusVMState;\nuse PVE::QemuServer;\n\nuse PVE::CLIHandler;\nuse base qw(PVE::CLIHandler);\n\nmy $upid_exit = sub {\n    my $upid = shift;\n    my $status = PVE::Tools::upid_read_status($upid);\n    exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0);\n};\n\nmy $nodename = PVE::INotify::nodename();\nmy %node = (node => $nodename);\n\nsub setup_environment {\n    PVE::RPCEnvironment->setup_default_cli_env();\n}\n\nsub run_vnc_proxy {\n    my ($path) = @_;\n\n    my $c;\n    while (++$c < 10 && !-e $path) { sleep(1); }\n\n    my $s = IO::Socket::UNIX->new(Peer => $path, Timeout => 120);\n\n    die \"unable to connect to socket '$path' - $!\" if !$s;\n\n    my $select = IO::Select->new();\n\n    $select->add(\\*STDIN);\n    $select->add($s);\n\n    my $timeout = 60 * 15; # 15 minutes\n\n    my @handles;\n    while (\n        $select->count\n        && scalar(@handles = $select->can_read($timeout))\n    ) {\n        foreach my $h (@handles) {\n            my $buf;\n            my $n = $h->sysread($buf, 4096);\n\n            if ($h == \\*STDIN) {\n                if ($n) {\n                    syswrite($s, $buf);\n                } else {\n                    exit(0);\n                }\n            } elsif ($h == $s) {\n                if ($n) {\n                    syswrite(\\*STDOUT, $buf);\n                } else {\n                    exit(0);\n                }\n            }\n        }\n    }\n    exit(0);\n}\n\nsub print_recursive_hash {\n    my ($prefix, $hash, $key) = @_;\n\n    if (ref($hash) eq 'HASH') {\n        if (defined($key)) {\n            print \"$prefix$key:\\n\";\n        }\n        for my $itemkey (sort keys %$hash) {\n            print_recursive_hash(\"\\t$prefix\", $hash->{$itemkey}, $itemkey);\n        }\n    } elsif (ref($hash) eq 'ARRAY') {\n        if (defined($key)) {\n            print \"$prefix$key:\\n\";\n        }\n        for my $item (@$hash) {\n            print_recursive_hash(\"\\t$prefix\", $item);\n        }\n    } elsif ((!ref($hash) && defined($hash)) || ref($hash) eq 'JSON::PP::Boolean') {\n        if (defined($key)) {\n            print \"$prefix$key: $hash\\n\";\n        } else {\n            print \"$prefix$hash\\n\";\n        }\n    }\n}\n\n__PACKAGE__->register_method({\n    name => 'showcmd',\n    path => 'showcmd',\n    method => 'GET',\n    description => \"Show command line which is used to start the VM (debug info).\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            pretty => {\n                description => \"Puts each option on a new line to enhance human readability\",\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n            },\n            snapshot => get_standard_option(\n                'pve-snapshot-name',\n                {\n                    description => \"Fetch config values from given snapshot.\",\n                    optional => 1,\n                    completion => sub {\n                        my ($cmd, $pname, $cur, $args) = @_;\n                        PVE::QemuConfig->snapshot_list($args->[0]);\n                    },\n                },\n            ),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $storecfg = PVE::Storage::config();\n        my $cmdline =\n            PVE::QemuServer::vm_commandline($storecfg, $param->{vmid}, $param->{snapshot});\n\n        $cmdline =~ s/ -/ \\\\\\n  -/g if $param->{pretty};\n\n        print \"$cmdline\\n\";\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'remote_migrate_vm',\n    path => 'remote_migrate_vm',\n    method => 'POST',\n    description =>\n        \"Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!\",\n    permissions => {\n        check => ['perm', '/vms/{vmid}', ['VM.Migrate']],\n    },\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            'target-vmid' => get_standard_option('pve-vmid', { optional => 1 }),\n            'target-endpoint' => get_standard_option('proxmox-remote', {\n                    description => \"Remote target endpoint\",\n            }),\n            online => {\n                type => 'boolean',\n                description =>\n                    \"Use online/live migration if VM is running. Ignored if VM is stopped.\",\n                optional => 1,\n            },\n            delete => {\n                type => 'boolean',\n                description =>\n                    \"Delete the original VM and related data after successful migration. By default the original VM is kept on the source cluster in a stopped state.\",\n                optional => 1,\n                default => 0,\n            },\n            'target-storage' => get_standard_option(\n                'pve-targetstorage',\n                {\n                    completion => \\&PVE::QemuServer::complete_migration_storage,\n                    optional => 0,\n                },\n            ),\n            'target-bridge' => {\n                type => 'string',\n                description =>\n                    \"Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.\",\n                format => 'bridge-pair-list',\n            },\n            bwlimit => {\n                description => \"Override I/O bandwidth limit (in KiB/s).\",\n                optional => 1,\n                type => 'integer',\n                minimum => '0',\n                default => 'migrate limit from datacenter or storage config',\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n        description => \"the task ID.\",\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $source_vmid = $param->{vmid};\n        my $target_endpoint = $param->{'target-endpoint'};\n        my $target_vmid = $param->{'target-vmid'} // $source_vmid;\n\n        my $remote = PVE::JSONSchema::parse_property_string('proxmox-remote', $target_endpoint);\n\n        # TODO: move this as helper somewhere appropriate?\n        my $conn_args = {\n            protocol => 'https',\n            host => $remote->{host},\n            port => $remote->{port} // 8006,\n            apitoken => $remote->{apitoken},\n        };\n\n        $conn_args->{cached_fingerprints} = { uc($remote->{fingerprint}) => 1 }\n            if defined($remote->{fingerprint});\n\n        my $api_client = PVE::APIClient::LWP->new(%$conn_args);\n        my $resources = $api_client->get(\"/cluster/resources\", { type => 'vm' });\n        if (grep { defined($_->{vmid}) && $_->{vmid} eq $target_vmid } @$resources) {\n            raise_param_exc(\n                {\n                    target_vmid =>\n                        \"Guest with ID '$target_vmid' already exists on remote cluster\",\n                },\n            );\n        }\n\n        my $storages = $api_client->get(\"/nodes/localhost/storage\", { enabled => 1 });\n\n        my $storecfg = PVE::Storage::config();\n        my $target_storage = $param->{'target-storage'};\n        my $storagemap =\n            eval { PVE::JSONSchema::parse_idmap($target_storage, 'pve-storage-id') };\n        raise_param_exc({ 'target-storage' => \"failed to parse storage map: $@\" })\n            if $@;\n\n        my $check_remote_storage = sub {\n            my ($storage) = @_;\n            my $found = [grep { $_->{storage} eq $storage } @$storages];\n            die \"remote: storage '$storage' does not exist (or missing permission)!\\n\"\n                if !@$found;\n\n            $found = @$found[0];\n\n            my $content_types = [PVE::Tools::split_list($found->{content})];\n            die \"remote: storage '$storage' cannot store images\\n\"\n                if !grep { $_ eq 'images' } @$content_types;\n        };\n\n        foreach my $target_sid (values %{ $storagemap->{entries} }) {\n            $check_remote_storage->($target_sid);\n        }\n\n        $check_remote_storage->($storagemap->{default})\n            if $storagemap->{default};\n\n        return PVE::API2::Qemu->remote_migrate_vm($param);\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'status',\n    path => 'status',\n    method => 'GET',\n    description => \"Show VM status.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            verbose => {\n                description => \"Verbose output format\",\n                type => 'boolean',\n                optional => 1,\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        # test if VM exists\n        my $conf = PVE::QemuConfig->load_config($param->{vmid});\n\n        my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);\n        my $stat = $vmstatus->{ $param->{vmid} };\n        if ($param->{verbose}) {\n            foreach my $k (sort (keys %$stat)) {\n                next if $k eq 'cpu' || $k eq 'relcpu'; # always 0\n                my $v = $stat->{$k};\n                print_recursive_hash(\"\", $v, $k);\n            }\n        } else {\n            my $status = $stat->{qmpstatus} || 'unknown';\n            print \"status: $status\\n\";\n        }\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vncproxy',\n    path => 'vncproxy',\n    method => 'PUT',\n    description => \"Proxy VM VNC traffic to stdin/stdout\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        PVE::QemuConfig::assert_config_exists_on_node($vmid);\n        my $vnc_socket = PVE::QemuServer::Helpers::vnc_socket($vmid);\n\n        if (my $ticket = $ENV{LC_PVE_TICKET}) { # NOTE: ssh on debian only pass LC_* variables\n            mon_cmd($vmid, \"set_password\", protocol => 'vnc', password => $ticket);\n            mon_cmd($vmid, \"expire_password\", protocol => 'vnc', time => \"+30\");\n        } else {\n            die \"LC_PVE_TICKET not set, VNC proxy without password is forbidden\\n\";\n        }\n\n        run_vnc_proxy($vnc_socket);\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'unlock',\n    path => 'unlock',\n    method => 'PUT',\n    description => \"Unlock the VM.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n\n        PVE::QemuConfig->lock_config(\n            $vmid,\n            sub {\n                my $conf = PVE::QemuConfig->load_config($vmid);\n                delete $conf->{lock};\n                delete $conf->{pending}->{lock} if $conf->{pending}; # just to be sure\n                PVE::QemuConfig->write_config($vmid, $conf);\n            },\n        );\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'nbdstop',\n    path => 'nbdstop',\n    method => 'PUT',\n    description => \"Stop embedded nbd server.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n\n        eval { PVE::QemuServer::QMPHelpers::nbd_stop($vmid) };\n        warn $@ if $@;\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'mtunnel',\n    path => 'mtunnel',\n    method => 'POST',\n    description => \"Used by qmigrate - do not use manually.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {},\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        if (!PVE::Cluster::check_cfs_quorum(1)) {\n            print \"no quorum\\n\";\n            return;\n        }\n\n        my $tunnel_write = sub {\n            my $text = shift;\n            chomp $text;\n            print \"$text\\n\";\n            *STDOUT->flush();\n        };\n\n        $tunnel_write->(\"tunnel online\");\n        $tunnel_write->(\"ver 1\");\n\n        while (my $line = <STDIN>) {\n            chomp $line;\n            if ($line =~ /^quit$/) {\n                $tunnel_write->(\"OK\");\n                last;\n            } elsif ($line =~ /^resume (\\d+)$/) {\n                my $vmid = $1;\n                # check_running and vm_resume with nocheck, since local node\n                # might not have processed config move/rename yet\n                if (PVE::QemuServer::check_running($vmid, 1)) {\n                    eval { PVE::QemuServer::RunState::vm_resume($vmid, 1, 1); };\n                    if ($@) {\n                        $tunnel_write->(\"ERR: resume failed - $@\");\n                    } else {\n                        $tunnel_write->(\"OK\");\n                    }\n                } else {\n                    $tunnel_write->(\"ERR: resume failed - VM $vmid not running\");\n                }\n            }\n        }\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'wait',\n    path => 'wait',\n    method => 'GET',\n    description => \"Wait until the VM is stopped.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            timeout => {\n                description => \"Timeout in seconds. Default is to wait forever.\",\n                type => 'integer',\n                minimum => 1,\n                optional => 1,\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $timeout = $param->{timeout};\n\n        my $pid = PVE::QemuServer::check_running($vmid);\n        return if !$pid;\n\n        print \"waiting until VM $vmid stops (PID $pid)\\n\";\n\n        my $count = 0;\n        while ((!$timeout || ($count < $timeout)) && PVE::QemuServer::check_running($vmid)) {\n            $count++;\n            sleep 1;\n        }\n\n        die \"wait failed - got timeout\\n\" if PVE::QemuServer::check_running($vmid);\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'monitor',\n    path => 'monitor',\n    method => 'POST',\n    description => \"Enter QEMU Monitor interface.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n\n        my $conf = PVE::QemuConfig->load_config($vmid); # check if VM exists\n\n        print \"Entering QEMU Monitor for VM $vmid - type 'help' for help\\n\";\n\n        my $term = Term::ReadLine->new('qm');\n\n        while (defined(my $input = $term->readline('qm> '))) {\n            chomp $input;\n            next if $input =~ m/^\\s*$/;\n            last if $input =~ m/^\\s*q(uit)?\\s*$/;\n\n            eval { print PVE::QemuServer::Monitor::hmp_cmd($vmid, $input, 30) };\n            print \"ERROR: $@\" if $@;\n        }\n\n        return;\n\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'rescan',\n    path => 'rescan',\n    method => 'POST',\n    description => \"Rescan all storages and update disk sizes and unused disk images.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                {\n                    optional => 1,\n                    completion => \\&PVE::QemuServer::complete_vmid,\n                },\n            ),\n            dryrun => {\n                type => 'boolean',\n                optional => 1,\n                default => 0,\n                description => 'Do not actually write changes out to VM config(s).',\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $dryrun = $param->{dryrun};\n\n        print \"NOTE: running in dry-run mode, won't write changes out!\\n\" if $dryrun;\n\n        PVE::QemuServer::rescan($param->{vmid}, 0, $dryrun);\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'importdisk',\n    path => 'importdisk',\n    method => 'POST',\n    description => \"Import an external disk image as an unused disk in a VM. The\n image format has to be supported by qemu-img(1).\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n            source => {\n                description => 'Path to the disk image to import',\n                type => 'string',\n                optional => 0,\n            },\n            storage => get_standard_option(\n                'pve-storage-id',\n                {\n                    description => 'Target storage ID',\n                    completion => \\&PVE::QemuServer::complete_storage,\n                    optional => 0,\n                },\n            ),\n            format => {\n                type => 'string',\n                description => 'Target format',\n                enum => ['raw', 'qcow2', 'vmdk'],\n                optional => 1,\n            },\n            'target-disk' => {\n                type => 'string',\n                description =>\n                    'The disk name where the volume will be imported to (e.g. scsi1).',\n                enum => [PVE::QemuServer::Drive::valid_drive_names_with_unused()],\n                optional => 1,\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = extract_param($param, 'vmid');\n        my $source = extract_param($param, 'source');\n        my $storeid = extract_param($param, 'storage');\n        my $format = extract_param($param, 'format');\n        my $target_disk = extract_param($param, 'target-disk');\n\n        # do_import does not allow invalid drive names (e.g. unused0)\n        $target_disk = undef if $target_disk && !is_valid_drivename($target_disk);\n\n        my $vm_conf = PVE::QemuConfig->load_config($vmid);\n        PVE::QemuConfig->check_lock($vm_conf);\n        die \"$source: non-existent or non-regular file\\n\" if (!-f $source);\n\n        my $storecfg = PVE::Storage::config();\n        PVE::Storage::storage_check_enabled($storecfg, $storeid);\n\n        my $target_storage_config = PVE::Storage::storage_config($storecfg, $storeid);\n        die \"storage $storeid does not support vm images\\n\"\n            if !$target_storage_config->{content}->{images};\n\n        print \"importing disk '$source' to VM $vmid ...\\n\";\n\n        my $size = PVE::Storage::file_size_info($source, undef, 'auto-detect');\n        my ($drive_id, $volid) = PVE::QemuServer::ImportDisk::do_import(\n            $source,\n            $size,\n            $vmid,\n            $storeid,\n            {\n                drive_name => $target_disk,\n                format => $format,\n            },\n        );\n\n        $vm_conf = PVE::QemuConfig->load_config($vmid);\n\n        # change imported _used_ disk to a base volume in case the VM is a template\n        PVE::QemuServer::template_create($vmid, $vm_conf, $drive_id)\n            if is_valid_drivename($drive_id) && PVE::QemuConfig->is_template($vm_conf);\n\n        print \"$drive_id: successfully imported disk '$vm_conf->{$drive_id}'\\n\";\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'enroll-efi-keys',\n    path => 'enroll-efi-keys',\n    method => 'POST',\n    description =>\n        \"Enroll important updated certificates to the EFI disk with pre-enrolled-keys. Currently,\"\n        . \" these are UEFI 2023 certificates from Microsoft. Must be called while the VM is shut\"\n        . \" down.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid =>\n                get_standard_option('pve-vmid', { completion => \\&PVE::QemuServer::complete_vmid }),\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = extract_param($param, 'vmid');\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n        PVE::QemuConfig->check_lock($conf);\n\n        die \"VM $vmid is running\\n\" if PVE::QemuServer::Helpers::vm_running_locally($vmid);\n        die \"VM $vmid is a template\\n\" if PVE::QemuConfig->is_template($conf);\n        die \"VM $vmid has no EFI disk configured\\n\" if !$conf->{efidisk0};\n\n        my $storecfg = PVE::Storage::config();\n\n        my $efidisk = parse_drive('efidisk0', $conf->{efidisk0});\n        my $updated =\n            PVE::QemuServer::OVMF::ensure_ms_2023_cert_enrolled($storecfg, $vmid, $efidisk);\n\n        if (!$updated) {\n            print \"skipping - no pre-enrolled keys or already got ms-cert=2023k marker\\n\";\n            return;\n        }\n\n        PVE::QemuConfig->lock_config(\n            $vmid,\n            sub {\n                my $locked_conf = PVE::QemuConfig->load_config($vmid);\n\n                eval { PVE::Tools::assert_if_modified($conf->{digest}, $locked_conf->{digest}) };\n                die \"VM ${vmid}: $@\" if $@;\n\n                $locked_conf->{efidisk0} = print_drive($updated);\n                PVE::QemuConfig->write_config($vmid, $locked_conf);\n                print \"successfully updated efidisk\\n\";\n            },\n        );\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'terminal',\n    path => 'terminal',\n    method => 'POST',\n    description =>\n        \"Open a terminal using a serial device (The VM need to have a serial device configured, for example 'serial0: socket')\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            iface => {\n                description =>\n                    \"Select the serial device. By default we simply use the first suitable device.\",\n                type => 'string',\n                optional => 1,\n                enum => [qw(serial0 serial1 serial2 serial3)],\n            },\n            escape => {\n                description => \"Escape character.\",\n                type => 'string',\n                optional => 1,\n                default => '^O',\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n\n        my $escape = $param->{escape} // '^O';\n        if ($escape =~ /^\\^([\\x40-\\x7a])$/) {\n            $escape = ord($1) & 0x1F;\n        } elsif ($escape =~ /^0x[0-9a-f]+$/i) {\n            $escape = hex($escape);\n        } elsif ($escape =~ /^[0-9]+$/) {\n            $escape = int($escape);\n        } else {\n            die \"invalid escape character definition: $escape\\n\";\n        }\n        my $escapemsg = '';\n        if ($escape) {\n            $escapemsg = sprintf(' (press Ctrl+%c to exit)', $escape + 0x40);\n            $escape = sprintf(',escape=0x%x', $escape);\n        } else {\n            $escape = '';\n        }\n\n        my $conf = PVE::QemuConfig->load_config($vmid); # check if VM exists\n\n        my $iface = $param->{iface};\n\n        if ($iface) {\n            die \"serial interface '$iface' is not configured\\n\" if !$conf->{$iface};\n            die \"wrong serial type on interface '$iface'\\n\" if $conf->{$iface} ne 'socket';\n        } else {\n            foreach my $opt (qw(serial0 serial1 serial2 serial3)) {\n                if ($conf->{$opt} && ($conf->{$opt} eq 'socket')) {\n                    $iface = $opt;\n                    last;\n                }\n            }\n            die \"unable to find a serial interface\\n\" if !$iface;\n        }\n\n        die \"VM $vmid not running\\n\" if !PVE::QemuServer::check_running($vmid);\n\n        my $socket = \"/var/run/qemu-server/${vmid}.$iface\";\n\n        my $cmd = \"socat UNIX-CONNECT:$socket STDIO,raw,echo=0$escape\";\n\n        print \"starting serial terminal on interface ${iface}${escapemsg}\\n\";\n\n        system($cmd);\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'importovf',\n    path => 'importovf',\n    description => \"Create a new VM using parameters read from an OVF manifest\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::Cluster::complete_next_vmid },\n            ),\n            manifest => {\n                type => 'string',\n                description => 'path to the ovf file',\n            },\n            storage => get_standard_option(\n                'pve-storage-id',\n                {\n                    description => 'Target storage ID',\n                    completion => \\&PVE::QemuServer::complete_storage,\n                    optional => 0,\n                },\n            ),\n            format => {\n                type => 'string',\n                description => 'Target format',\n                enum => ['raw', 'qcow2', 'vmdk'],\n                optional => 1,\n            },\n            dryrun => {\n                type => 'boolean',\n                description =>\n                    'Print a parsed representation of the extracted OVF parameters, but do not create a VM',\n                optional => 1,\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = PVE::Tools::extract_param($param, 'vmid');\n        my $ovf_file = PVE::Tools::extract_param($param, 'manifest');\n        my $storeid = PVE::Tools::extract_param($param, 'storage');\n        my $format = PVE::Tools::extract_param($param, 'format');\n        my $dryrun = PVE::Tools::extract_param($param, 'dryrun');\n\n        die \"$ovf_file: non-existent or non-regular file\\n\" if (!-f $ovf_file);\n        my $storecfg = PVE::Storage::config();\n        PVE::Storage::storage_check_enabled($storecfg, $storeid);\n\n        my $parsed = PVE::GuestImport::OVF::parse_ovf($ovf_file);\n\n        if ($dryrun) {\n            print to_json($parsed, { pretty => 1, canonical => 1 });\n            return;\n        }\n\n        eval { PVE::QemuConfig->create_and_lock_config($vmid) };\n        die \"Reserving empty config for OVF import to VM $vmid failed: $@\" if $@;\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n        die \"Internal error: Expected 'create' lock in config of VM $vmid!\"\n            if !PVE::QemuConfig->has_lock($conf, \"create\");\n\n        $conf->{name} = $parsed->{qm}->{name} if defined($parsed->{qm}->{name});\n        $conf->{memory} = $parsed->{qm}->{memory} if defined($parsed->{qm}->{memory});\n        $conf->{cores} = $parsed->{qm}->{cores} if defined($parsed->{qm}->{cores});\n\n        my $imported_disks = [];\n        eval {\n            # order matters, as do_import() will load_config() internally\n            $conf->{vmgenid} = PVE::QemuServer::generate_uuid();\n            $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();\n            PVE::QemuConfig->write_config($vmid, $conf);\n\n            foreach my $disk (@{ $parsed->{disks} }) {\n                my ($file, $drive) = ($disk->{backing_file}, $disk->{disk_address});\n                my $size = PVE::Storage::file_size_info($file, undef, 'auto-detect');\n                my ($name, $volid) = PVE::QemuServer::ImportDisk::do_import(\n                    $file,\n                    $size,\n                    $vmid,\n                    $storeid,\n                    {\n                        drive_name => $drive,\n                        format => $format,\n                        skiplock => 1,\n                    },\n                );\n                # for cleanup on (later) error\n                push @$imported_disks, $volid;\n            }\n\n            # reload after disks entries have been created\n            $conf = PVE::QemuConfig->load_config($vmid);\n            my $devs = PVE::QemuServer::get_default_bootdevices($conf);\n            $conf->{boot} = PVE::QemuServer::print_bootorder($devs);\n            PVE::QemuConfig->write_config($vmid, $conf);\n        };\n\n        if (my $err = $@) {\n            my $skiplock = 1;\n            warn \"error during import, cleaning up created resources...\\n\";\n            for my $volid (@$imported_disks) {\n                eval { PVE::Storage::vdisk_free($storecfg, $volid) };\n                warn \"cleanup of $volid failed: $@\\n\" if $@;\n            }\n            eval { PVE::QemuServer::destroy_vm($storecfg, $vmid, $skiplock) };\n            warn \"Could not destroy VM $vmid: $@\" if \"$@\";\n            die \"import failed - $err\";\n        }\n\n        PVE::QemuConfig->remove_lock($vmid, \"create\");\n\n        return;\n\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'exec',\n    path => 'exec',\n    method => 'POST',\n    protected => 1,\n    description => \"Executes the given command via the guest agent\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            synchronous => {\n                type => 'boolean',\n                optional => 1,\n                default => 1,\n                description =>\n                    \"If set to off, returns the pid immediately instead of waiting for the command to finish or the timeout.\",\n            },\n            'timeout' => {\n                type => 'integer',\n                description =>\n                    \"The maximum time to wait synchronously for the command to finish. If reached, the pid gets returned. Set to 0 to deactivate\",\n                minimum => 0,\n                optional => 1,\n                default => 30,\n            },\n            'pass-stdin' => {\n                type => 'boolean',\n                description =>\n                    \"When set, read STDIN until EOF and forward to guest agent via 'input-data' (usually treated as STDIN to process launched by guest agent). Allows maximal 1 MiB.\",\n                optional => 1,\n                default => 0,\n            },\n            'extra-args' => get_standard_option('extra-args'),\n        },\n    },\n    returns => {\n        type => 'object',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $sync = $param->{synchronous} // 1;\n        my $pass_stdin = $param->{'pass-stdin'};\n        if (defined($param->{timeout}) && !$sync) {\n            raise_param_exc({ synchronous => \"needs to be set for 'timeout'\" });\n        }\n\n        my $input_data = undef;\n        if ($pass_stdin) {\n            $input_data = '';\n            while (my $line = <STDIN>) {\n                $input_data .= $line;\n                if (length($input_data) > 1024 * 1024) {\n                    # not sure how QEMU handles large amounts of data being\n                    # passed into the QMP socket, so limit to be safe\n                    die \"'input-data' (STDIN) is limited to 1 MiB, aborting\\n\";\n                }\n            }\n        }\n\n        my $args = $param->{'extra-args'};\n        $args = undef if !$args || !@$args;\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $conf, $input_data, $args);\n\n        if ($sync) {\n            my $pid = $res->{pid};\n            my $timeout = $param->{timeout} // 30;\n            my $starttime = time();\n\n            while ($timeout == 0 || (time() - $starttime) < $timeout) {\n                my $out = PVE::QemuServer::Agent::qemu_exec_status($vmid, $conf, $pid);\n                if ($out->{exited}) {\n                    $res = $out;\n                    last;\n                }\n                sleep 1;\n            }\n\n            if (!$res->{exited}) {\n                warn \"timeout reached, returning pid\\n\";\n            }\n        }\n\n        return { result => $res };\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'cleanup',\n    path => 'cleanup',\n    method => 'POST',\n    protected => 1,\n    description =>\n        \"Cleans up resources like tap devices, vgpus, etc. Called after a vm shuts down, crashes, etc.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            node => get_standard_option('pve-node'),\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::QemuServer::complete_vmid_running },\n            ),\n            'clean-shutdown' => {\n                type => 'boolean',\n                description => \"Indicates if qemu shutdown cleanly.\",\n            },\n            'guest-requested' => {\n                type => 'boolean',\n                description =>\n                    \"Indicates if the shutdown was requested by the guest or via qmp.\",\n            },\n        },\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my $vmid = $param->{vmid};\n        my $clean = $param->{'clean-shutdown'};\n        my $guest = $param->{'guest-requested'};\n        my $restart = 0;\n\n        # return if we do not have the config anymore\n        return if !-f PVE::QemuConfig->config_file($vmid);\n\n        my $storecfg = PVE::Storage::config();\n        warn \"Starting cleanup for $vmid\\n\";\n\n        # mdev cleanup can take a while, so wait up to 60 seconds\n        PVE::QemuConfig->lock_config_full(\n            $vmid,\n            60,\n            sub {\n                my $conf = PVE::QemuConfig->load_config($vmid);\n                my $pid = PVE::QemuServer::check_running($vmid);\n                die \"vm still running\\n\" if $pid;\n\n                # Rollback already does cleanup when preparing and afterwards temporarily drops the\n                # lock on the configuration file to rollback the volumes. Deactivating volumes here\n                # again while that is happening would be problematic.\n                die \"skipping cleanup - 'rollback' lock is present\\n\"\n                    if $conf->{lock} && $conf->{lock} eq 'rollback';\n\n                if (!$clean) {\n                    # we have to cleanup the tap devices after a crash\n\n                    foreach my $opt (keys %$conf) {\n                        next if $opt !~ m/^net(\\d+)$/;\n                        my $interface = $1;\n                        PVE::Network::tap_unplug(\"tap${vmid}i${interface}\");\n                    }\n                }\n\n                if (!$clean || $guest) {\n                    # vm was shutdown from inside the guest or crashed, doing api cleanup\n                    PVE::QemuServer::vm_stop_cleanup($storecfg, $vmid, $conf, 0, 0, 1);\n                }\n\n                PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-stop');\n\n                $restart = eval { PVE::QemuServer::clear_reboot_request($vmid) };\n                warn $@ if $@;\n            },\n        );\n\n        warn \"Finished cleanup for $vmid\\n\";\n\n        if ($restart) {\n            warn \"Restarting VM $vmid\\n\";\n            PVE::API2::Qemu->vm_start({\n                vmid => $vmid,\n                %node,\n            });\n        }\n\n        return;\n    },\n});\n\n__PACKAGE__->register_method({\n    name => 'vm_import',\n    path => 'vm-import',\n    description =>\n        \"Import a foreign virtual guest from a supported import source, such as an ESXi storage.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => PVE::QemuServer::json_config_properties(\n            {\n                vmid => get_standard_option(\n                    'pve-vmid',\n                    { completion => \\&PVE::Cluster::complete_next_vmid },\n                ),\n                'source' => {\n                    type => 'string',\n                    description => 'The import source volume id.',\n                },\n                storage => get_standard_option(\n                    'pve-storage-id',\n                    {\n                        description => \"Default storage.\",\n                        completion => \\&PVE::QemuServer::complete_storage,\n                    },\n                ),\n                'live-import' => {\n                    type => 'boolean',\n                    optional => 1,\n                    default => 0,\n                    description =>\n                        \"Immediately start the VM and copy the data in the background.\",\n                },\n                'dryrun' => {\n                    type => 'boolean',\n                    optional => 1,\n                    default => 0,\n                    description => \"Show the create command and exit without doing anything.\",\n                },\n                delete => {\n                    type => 'string',\n                    format => 'pve-configid-list',\n                    description => \"A list of settings you want to delete.\",\n                    optional => 1,\n                },\n                format => {\n                    type => 'string',\n                    description => 'Target format',\n                    enum => ['raw', 'qcow2', 'vmdk'],\n                    optional => 1,\n                },\n            },\n            1, # with_disk_alloc\n        ),\n    },\n    returns => { type => 'null' },\n    code => sub {\n        my ($param) = @_;\n\n        my ($vmid, $source, $storage, $format, $live_import, $dryrun, $delete) =\n            delete $param->@{qw(vmid source storage format live-import dryrun delete)};\n\n        if (defined($format)) {\n            $format = \",format=$format\";\n        } else {\n            $format = '';\n        }\n\n        my $storecfg = PVE::Storage::config();\n        my $metadata = PVE::Storage::get_import_metadata($storecfg, $source);\n\n        my $create_args = $metadata->{'create-args'};\n        if (my $netdevs = $metadata->{net}) {\n            for my $net (keys $netdevs->%*) {\n                my $value = $netdevs->{$net};\n                $create_args->{$net} =\n                    join(',', map { $_ . '=' . $value->{$_} } sort keys %$value);\n            }\n        }\n        if (my $disks = $metadata->{disks}) {\n            if (delete $disks->{efidisk0}) {\n                $create_args->{efidisk0} = \"$storage:1$format,efitype=4m\";\n            }\n            for my $disk (keys $disks->%*) {\n                my $value = $disks->{$disk}->{volid};\n                $create_args->{$disk} = \"$storage:0${format},import-from=$value\";\n            }\n        }\n\n        $create_args->{'live-restore'} = 1 if $live_import;\n\n        $create_args->{$_} = $param->{$_} for keys $param->%*;\n        delete $create_args->{$_} for PVE::Tools::split_list($delete);\n\n        if ($dryrun) {\n            print(\"# dry-run – the resulting create command for the import would be:\\n\");\n            print(\"qm create $vmid \\\\\\n  \");\n            print(join(\n                \" \\\\\\n  \", map { \"--$_ $create_args->{$_}\" } sort keys $create_args->%*));\n            print(\"\\n\");\n            return;\n        }\n\n        PVE::API2::Qemu->create_vm({\n            %node,\n            vmid => $vmid,\n            %$create_args,\n        });\n        return;\n    },\n});\n\nmy $print_agent_result = sub {\n    my ($data) = @_;\n\n    my $result = $data->{result} // $data;\n    return if !defined($result);\n\n    my $class = ref($result);\n\n    if (!$class) {\n        chomp $result;\n        return if $result =~ m/^\\s*$/;\n        print \"$result\\n\";\n        return;\n    }\n\n    if (($class eq 'HASH') && !scalar(keys %$result)) { # empty hash\n        return;\n    }\n\n    print to_json($result, { pretty => 1, canonical => 1, utf8 => 1 });\n};\n\nsub param_mapping {\n    my ($name) = @_;\n\n    my $ssh_key_map = [\n        'sshkeys',\n        sub {\n            return URI::Escape::uri_escape(file_get_contents($_[0]));\n        },\n    ];\n    my $cipassword_map =\n        PVE::CLIHandler::get_standard_mapping('pve-password', { name => 'cipassword' });\n    my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password');\n    my $mapping = {\n        'update_vm' => [$ssh_key_map, $cipassword_map],\n        'create_vm' => [$ssh_key_map, $cipassword_map],\n        'set-user-password' => [$password_map],\n    };\n\n    return $mapping->{$name};\n}\n\nour $cmddef = {\n    list => [\n        \"PVE::API2::Qemu\",\n        'vmlist',\n        [],\n        {%node},\n        sub {\n            my $vmlist = shift;\n            exit 0 if (!scalar(@$vmlist));\n\n            printf \"%10s %-20s %-10s %-10s %12s %-10s\\n\",\n                qw(VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID);\n\n            foreach my $rec (sort { $a->{vmid} <=> $b->{vmid} } @$vmlist) {\n                printf \"%10s %-20s %-10s %-10s %12.2f %-10s\\n\", $rec->{vmid}, $rec->{name},\n                    $rec->{qmpstatus} || $rec->{status},\n                    ($rec->{maxmem} || 0) / (1024 * 1024),\n                    ($rec->{maxdisk} || 0) / (1024 * 1024 * 1024),\n                    $rec->{pid} || 0;\n            }\n        },\n    ],\n\n    create => [\"PVE::API2::Qemu\", 'create_vm', ['vmid'], {%node}, $upid_exit],\n    destroy => [\"PVE::API2::Qemu\", 'destroy_vm', ['vmid'], {%node}, $upid_exit],\n    clone => [\"PVE::API2::Qemu\", 'clone_vm', ['vmid', 'newid'], {%node}, $upid_exit],\n\n    migrate => [\"PVE::API2::Qemu\", 'migrate_vm', ['vmid', 'target'], {%node}, $upid_exit],\n    'remote-migrate' => [\n        __PACKAGE__,\n        'remote_migrate_vm',\n        ['vmid', 'target-vmid', 'target-endpoint'],\n        {%node},\n        $upid_exit,\n    ],\n\n    set => [\"PVE::API2::Qemu\", 'update_vm', ['vmid'], {%node}],\n\n    config => [\n        \"PVE::API2::Qemu\",\n        'vm_config',\n        ['vmid'],\n        {%node},\n        sub {\n            my $config = shift;\n            foreach my $k (sort (keys %$config)) {\n                next if $k eq 'digest';\n                my $v = $config->{$k};\n                if ($k eq 'description') {\n                    $v = PVE::Tools::encode_text($v);\n                }\n                print \"$k: $v\\n\";\n            }\n        },\n    ],\n\n    pending =>\n        [\"PVE::API2::Qemu\", 'vm_pending', ['vmid'], {%node}, \\&PVE::GuestHelpers::format_pending],\n    showcmd => [__PACKAGE__, 'showcmd', ['vmid']],\n\n    status => [__PACKAGE__, 'status', ['vmid']],\n\n    # FIXME: for 8.0 move to command group snapshot { create, list, destroy, rollback }\n    snapshot => [\"PVE::API2::Qemu\", 'snapshot', ['vmid', 'snapname'], {%node}, $upid_exit],\n    delsnapshot =>\n        [\"PVE::API2::Qemu\", 'delsnapshot', ['vmid', 'snapname'], {%node}, $upid_exit],\n    listsnapshot => [\n        \"PVE::API2::Qemu\",\n        'snapshot_list',\n        ['vmid'],\n        {%node},\n        \\&PVE::GuestHelpers::print_snapshot_tree,\n    ],\n    rollback => [\"PVE::API2::Qemu\", 'rollback', ['vmid', 'snapname'], {%node}, $upid_exit],\n\n    template => [\"PVE::API2::Qemu\", 'template', ['vmid'], {%node}],\n\n    # FIXME: should be in a power command group?\n    start => [\"PVE::API2::Qemu\", 'vm_start', ['vmid'], {%node}, $upid_exit],\n    stop => [\"PVE::API2::Qemu\", 'vm_stop', ['vmid'], {%node}, $upid_exit],\n    reset => [\"PVE::API2::Qemu\", 'vm_reset', ['vmid'], {%node}, $upid_exit],\n    shutdown => [\"PVE::API2::Qemu\", 'vm_shutdown', ['vmid'], {%node}, $upid_exit],\n    reboot => [\"PVE::API2::Qemu\", 'vm_reboot', ['vmid'], {%node}, $upid_exit],\n    suspend => [\"PVE::API2::Qemu\", 'vm_suspend', ['vmid'], {%node}, $upid_exit],\n    resume => [\"PVE::API2::Qemu\", 'vm_resume', ['vmid'], {%node}, $upid_exit],\n\n    sendkey => [\"PVE::API2::Qemu\", 'vm_sendkey', ['vmid', 'key'], {%node}],\n\n    vncproxy => [__PACKAGE__, 'vncproxy', ['vmid']],\n\n    wait => [__PACKAGE__, 'wait', ['vmid']],\n\n    unlock => [__PACKAGE__, 'unlock', ['vmid']],\n\n    # TODO: evaluate dropping below aliases for 8.0, if no usage is left\n    importdisk => { alias => 'disk import' },\n    'move-disk' => { alias => 'disk move' },\n    move_disk => { alias => 'disk move' },\n    rescan => { alias => 'disk rescan' },\n    resize => { alias => 'disk resize' },\n    unlink => { alias => 'disk unlink' },\n\n    disk => {\n        import => [__PACKAGE__, 'importdisk', ['vmid', 'source', 'storage']],\n        'move' =>\n            [\"PVE::API2::Qemu\", 'move_vm_disk', ['vmid', 'disk', 'storage'], {%node}, $upid_exit],\n        rescan => [__PACKAGE__, 'rescan', []],\n        resize => [\"PVE::API2::Qemu\", 'resize_vm', ['vmid', 'disk', 'size'], {%node}],\n        unlink => [\"PVE::API2::Qemu\", 'unlink', ['vmid'], {%node}],\n    },\n\n    'enroll-efi-keys' => [__PACKAGE__, 'enroll-efi-keys', ['vmid']],\n\n    monitor => [__PACKAGE__, 'monitor', ['vmid']],\n\n    agent => { alias => 'guest cmd' }, # FIXME: remove with PVE 8.0\n\n    guest => {\n        cmd =>\n            [\"PVE::API2::Qemu::Agent\", 'agent', ['vmid', 'command'], {%node}, $print_agent_result],\n        passwd =>\n            [\"PVE::API2::Qemu::Agent\", 'set-user-password', ['vmid', 'username'], {%node}],\n        exec => [__PACKAGE__, 'exec', ['vmid', 'extra-args'], {%node}, $print_agent_result],\n        'exec-status' => [\n            \"PVE::API2::Qemu::Agent\",\n            'exec-status',\n            ['vmid', 'pid'],\n            {%node},\n            $print_agent_result,\n        ],\n    },\n\n    mtunnel => [__PACKAGE__, 'mtunnel', []],\n\n    nbdstop => [__PACKAGE__, 'nbdstop', ['vmid']],\n\n    terminal => [__PACKAGE__, 'terminal', ['vmid']],\n\n    importovf => [__PACKAGE__, 'importovf', ['vmid', 'manifest', 'storage']],\n\n    cleanup => [__PACKAGE__, 'cleanup', ['vmid', 'clean-shutdown', 'guest-requested'], {%node}],\n\n    cloudinit => {\n        dump => [\n            \"PVE::API2::Qemu\",\n            'cloudinit_generated_config_dump',\n            ['vmid', 'type'],\n            {%node},\n            sub { print \"$_[0]\\n\"; },\n        ],\n        pending => [\n            \"PVE::API2::Qemu\",\n            'cloudinit_pending',\n            ['vmid'],\n            {%node},\n            \\&PVE::GuestHelpers::format_pending,\n        ],\n        update => [\"PVE::API2::Qemu\", 'cloudinit_update', ['vmid'], { node => $nodename }],\n    },\n\n    import => [__PACKAGE__, 'vm_import', ['vmid', 'source']],\n};\n\n1;\n"
  },
  {
    "path": "src/PVE/CLI/qmrestore.pm",
    "content": "package PVE::CLI::qmrestore;\n\nuse strict;\nuse warnings;\nuse PVE::SafeSyslog;\nuse PVE::Tools qw(extract_param);\nuse PVE::INotify;\nuse PVE::RPCEnvironment;\nuse PVE::CLIHandler;\nuse PVE::JSONSchema qw(get_standard_option);\nuse PVE::Cluster;\nuse PVE::QemuServer;\nuse PVE::API2::Qemu;\n\nuse base qw(PVE::CLIHandler);\n\nsub setup_environment {\n    PVE::RPCEnvironment->setup_default_cli_env();\n}\n\n__PACKAGE__->register_method({\n    name => 'qmrestore',\n    path => 'qmrestore',\n    method => 'POST',\n    description => \"Restore QemuServer vzdump backups.\",\n    parameters => {\n        additionalProperties => 0,\n        properties => {\n            vmid => get_standard_option(\n                'pve-vmid',\n                { completion => \\&PVE::Cluster::complete_next_vmid },\n            ),\n            archive => {\n                description => \"The backup file. You can pass '-' to read from standard input.\",\n                type => 'string',\n                maxLength => 255,\n                completion => \\&PVE::QemuServer::complete_backup_archives,\n            },\n            storage => get_standard_option(\n                'pve-storage-id',\n                {\n                    description => \"Default storage.\",\n                    optional => 1,\n                    completion => \\&PVE::QemuServer::complete_storage,\n                },\n            ),\n            force => {\n                optional => 1,\n                type => 'boolean',\n                description => \"Allow to overwrite existing VM.\",\n            },\n            unique => {\n                optional => 1,\n                type => 'boolean',\n                description => \"Assign a unique random ethernet address.\",\n            },\n            pool => {\n                optional => 1,\n                type => 'string',\n                format => 'pve-poolid',\n                description => \"Add the VM to the specified pool.\",\n            },\n            bwlimit => {\n                description => \"Override I/O bandwidth limit (in KiB/s).\",\n                optional => 1,\n                type => 'number',\n                minimum => '0',\n            },\n            'live-restore' => {\n                optional => 1,\n                type => 'boolean',\n                description =>\n                    \"Start the VM immediately from the backup and restore in background. PBS only.\",\n            },\n            start => {\n                optional => 1,\n                type => 'boolean',\n                default => 0,\n                description => \"Start VM after it was restored successfully.\",\n            },\n            'ha-managed' => {\n                optional => 1,\n                type => 'boolean',\n                default => 0,\n                description => \"Add the VM as a HA resource after it was restored.\",\n            },\n        },\n    },\n    returns => {\n        type => 'string',\n    },\n    code => sub {\n        my ($param) = @_;\n\n        $param->{node} = PVE::INotify::nodename();\n\n        return PVE::API2::Qemu->create_vm($param);\n    },\n});\n\nour $cmddef = [\n    __PACKAGE__,\n    'qmrestore',\n    ['archive', 'vmid'],\n    undef,\n    sub {\n        my $upid = shift;\n        my $status = PVE::Tools::upid_read_status($upid);\n        exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0);\n    },\n];\n\n1;\n"
  },
  {
    "path": "src/PVE/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nPERLSOURCE = \t\t\t\\\n\tQemuServer.pm\t\t\\\n\tQemuMigrate.pm\t\t\\\n\tQMPClient.pm\t\t\\\n\tQemuConfig.pm\n\n.PHONY: install\ninstall:\n\tinstall -d $(DESTDIR)$(PERLDIR)/PVE\n\tinstall -m 0644 $(PERLSOURCE) $(DESTDIR)$(PERLDIR)/PVE/\n\t$(MAKE) -C VZDump install\n\t$(MAKE) -C API2 install\n\t$(MAKE) -C CLI install\n\t$(MAKE) -C QemuConfig install\n\t$(MAKE) -C QemuMigrate install\n\t$(MAKE) -C QemuServer install\n"
  },
  {
    "path": "src/PVE/QMPClient.pm",
    "content": "package PVE::QMPClient;\n\nuse strict;\nuse warnings;\n\nuse IO::Multiplex;\nuse IO::Socket::UNIX;\nuse JSON;\nuse POSIX qw(EINTR EAGAIN);\nuse Scalar::Util qw(weaken);\nuse Time::HiRes qw(usleep gettimeofday tv_interval);\n\nuse PVE::IPCC;\nuse PVE::QemuServer::Helpers;\n\n# QEMU Monitor Protocol (QMP) client.\n#\n# This implementation uses IO::Multiplex (libio-multiplex-perl) and\n# allows you to issue qmp and qga commands to different VMs in parallel.\n\n# Note: qemu can only handle 1 connection, so we close connections asap\n\nsub new {\n    my ($class, $eventcb) = @_;\n\n    my $mux = IO::Multiplex->new();\n\n    my $self = bless {\n        mux => $mux,\n        queue_lookup => {}, # $fh => $queue_info\n        queue_info => {},\n    }, $class;\n\n    $self->{eventcb} = $eventcb if $eventcb;\n\n    $mux->set_callback_object($self);\n\n    # make sure perl doesn't believe this is a circular reference as we\n    # delete mux in DESTROY\n    weaken($mux->{_object});\n\n    return $self;\n}\n\n# Note: List of special QGA command. Those commands can close the connection\n# without sending a response.\n\nmy $qga_allow_close_cmds = {\n    'guest-shutdown' => 1,\n    'guest-suspend-ram' => 1,\n    'guest-suspend-disk' => 1,\n    'guest-suspend-hybrid' => 1,\n};\n\nmy $push_cmd_to_queue = sub {\n    my ($self, $peer, $cmd) = @_;\n\n    my $execute = $cmd->{execute} || die \"no command name specified\";\n\n    my $sname = PVE::QemuServer::Helpers::qmp_socket($peer);\n\n    $self->{queue_info}->{$sname} = { peer => $peer, sname => $sname, cmds => [] }\n        if !$self->{queue_info}->{$sname};\n\n    push @{ $self->{queue_info}->{$sname}->{cmds} }, $cmd;\n\n    return $self->{queue_info}->{$sname};\n};\n\n# add a single command to the queue for later execution\n# with queue_execute()\nsub queue_cmd {\n    my ($self, $peer, $callback, $execute, %params) = @_;\n\n    my $cmd = {};\n    $cmd->{execute} = $execute;\n    $cmd->{arguments} = \\%params;\n    $cmd->{callback} = $callback;\n\n    &$push_cmd_to_queue($self, $peer, $cmd);\n\n    return;\n}\n\n# execute a single command\nsub cmd {\n    my ($self, $peer, $cmd, $timeout, $noerr) = @_;\n\n    my $result;\n\n    my $callback = sub {\n        my ($id, $resp) = @_;\n        $result = $resp->{'return'};\n        $result = { error => $resp->{'error'} } if !defined($result) && $resp->{'error'};\n    };\n\n    die \"no command specified\" if !($cmd && $cmd->{execute});\n\n    $cmd->{callback} = $callback;\n    $cmd->{arguments} = {} if !defined($cmd->{arguments});\n\n    my $queue_info = &$push_cmd_to_queue($self, $peer, $cmd);\n\n    if (!$timeout) {\n        # hack: monitor sometime blocks\n        if ($cmd->{execute} eq 'query-migrate') {\n            $timeout = 60 * 60; # 1 hour\n        } elsif ($cmd->{execute} =~ m/^(eject|change)/) {\n            $timeout = 60; # note: cdrom mount command is slow\n        } elsif ($cmd->{execute} eq 'guest-fsfreeze-freeze') {\n            # consider using the guest_fsfreeze() helper in Agent.pm\n            #\n            # freeze syncs all guest FS, if we kill it it stays in an unfreezable\n            # locked state with high probability, so use an generous timeout\n            $timeout = 60 * 60; # 1 hour\n        } elsif ($cmd->{execute} eq 'guest-fsfreeze-thaw') {\n            # While it should return instantly or never (dead locked) for Linux guests,\n            # the variance for Windows guests can be big. And there might be hook scripts\n            # that are executed upon thaw, so use 3 minutes to be on the safe side.\n            $timeout = 3 * 60;\n        } elsif (\n            $cmd->{execute} eq 'blockdev-add'\n            || $cmd->{execute} eq 'blockdev-insert-medium'\n            || $cmd->{execute} eq 'block-export-add'\n            || $cmd->{execute} eq 'device_add'\n            || $cmd->{execute} eq 'device_del'\n            || $cmd->{execute} eq 'netdev_add'\n            || $cmd->{execute} eq 'netdev_del'\n            || $cmd->{execute} eq 'object-add'\n            || $cmd->{execute} eq 'object-del'\n        ) {\n            $timeout = 60;\n        } elsif (\n            $cmd->{execute} eq 'backup-cancel'\n            || $cmd->{execute} eq 'block-commit'\n            || $cmd->{execute} eq 'block-export-del'\n            || $cmd->{execute} eq 'block-stream'\n            || $cmd->{execute} eq 'blockdev-del'\n            || $cmd->{execute} eq 'blockdev-mirror'\n            || $cmd->{execute} eq 'blockdev-remove-medium'\n            || $cmd->{execute} eq 'blockdev-reopen'\n            || $cmd->{execute} eq 'block-job-cancel'\n            || $cmd->{execute} eq 'job-complete'\n            || $cmd->{execute} eq 'drive-mirror'\n            || $cmd->{execute} eq 'guest-fstrim'\n            || $cmd->{execute} eq 'guest-shutdown'\n            || $cmd->{execute} eq 'query-backup'\n            || $cmd->{execute} eq 'query-block-jobs'\n            || $cmd->{execute} eq 'query-savevm'\n            || $cmd->{execute} eq 'savevm-end'\n            || $cmd->{execute} eq 'savevm-start'\n        ) {\n            $timeout = 10 * 60; # 10 mins\n        } elsif (\n            $cmd->{execute} eq 'blockdev-snapshot-delete-internal-sync'\n            || $cmd->{execute} eq 'blockdev-snapshot-internal-sync'\n        ) {\n            $timeout = 60 * 60; # 1 hour\n        } else {\n            #  NOTE: if you came here as user and want to change this, try using IO-Threads first\n            # which move out quite some processing of the main thread, leaving more time for QMP\n            $timeout = 5; # default\n        }\n    }\n\n    $self->queue_execute($timeout, 2);\n\n    if (defined($queue_info->{error})) {\n        die \"$peer->{name} $peer->{type} command '$cmd->{execute}' failed - $queue_info->{error}\"\n            if !$noerr;\n        $result = { error => $queue_info->{error} };\n        $result->{'error-is-timeout'} = 1 if $queue_info->{'error-is-timeout'};\n    }\n\n    return $result;\n}\n\nmy $cmdid_seq = 0;\nmy $cmdid_seq_qga = 0;\n\nmy $next_cmdid = sub {\n    my ($qga) = @_;\n\n    if ($qga) {\n        $cmdid_seq_qga++;\n        return \"$$\" . \"0\" . $cmdid_seq_qga;\n    } else {\n        $cmdid_seq++;\n        return \"$$:$cmdid_seq\";\n    }\n};\n\nmy $lookup_queue_info = sub {\n    my ($self, $fh, $noerr) = @_;\n\n    my $queue_info = $self->{queue_lookup}->{$fh};\n    if (!$queue_info) {\n        warn \"internal error - unable to lookup queue info\" if !$noerr;\n        return;\n    }\n    return $queue_info;\n};\n\nmy $close_connection = sub {\n    my ($self, $queue_info) = @_;\n\n    if (my $fh = delete $queue_info->{fh}) {\n        delete $self->{queue_lookup}->{$fh};\n        $self->{mux}->close($fh);\n    }\n};\n\nmy $open_connection = sub {\n    my ($self, $queue_info, $timeout) = @_;\n\n    die \"duplicate call to open\" if defined($queue_info->{fh});\n\n    my $peer = $queue_info->{peer};\n    my ($peer_name, $sotype) = $peer->@{qw(name type)};\n\n    my $sname = PVE::QemuServer::Helpers::qmp_socket($peer);\n\n    $timeout = 1 if !$timeout;\n\n    my $fh;\n    my $starttime = [gettimeofday];\n    my $count = 0;\n\n    for (;;) {\n        $count++;\n        $fh = IO::Socket::UNIX->new(Peer => $sname, Blocking => 0, Timeout => 1);\n        last if $fh;\n        if ($! != EINTR && $! != EAGAIN) {\n            die \"unable to connect to $peer_name $sotype socket - $!\\n\";\n        }\n        my $elapsed = tv_interval($starttime, [gettimeofday]);\n        if ($elapsed >= $timeout) {\n            die\n                \"unable to connect to $peer_name $sotype socket - timeout after $count retries\\n\";\n        }\n        usleep(100000);\n    }\n\n    $queue_info->{fh} = $fh;\n\n    $self->{queue_lookup}->{$fh} = $queue_info;\n\n    $self->{mux}->add($fh);\n    $self->{mux}->set_timeout($fh, $timeout);\n\n    return $fh;\n};\n\nmy $check_queue = sub {\n    my ($self) = @_;\n\n    my $running = 0;\n\n    foreach my $sname (keys %{ $self->{queue_info} }) {\n        my $queue_info = $self->{queue_info}->{$sname};\n        my $fh = $queue_info->{fh};\n        next if !$fh;\n\n        my $qga = $queue_info->{peer}->{type} eq 'qga';\n\n        if ($queue_info->{error}) {\n            &$close_connection($self, $queue_info);\n            next;\n        }\n\n        if ($queue_info->{current}) { # command running, waiting for response\n            $running++;\n            next;\n        }\n\n        if (!scalar(@{ $queue_info->{cmds} })) { # no more commands\n            &$close_connection($self, $queue_info);\n            next;\n        }\n\n        eval {\n\n            my $cmd = $queue_info->{current} = shift @{ $queue_info->{cmds} };\n            $cmd->{id} = &$next_cmdid($qga);\n\n            my $fd = -1;\n            if ($cmd->{execute} eq 'add-fd' || $cmd->{execute} eq 'getfd') {\n                $fd = $cmd->{arguments}->{fd};\n                delete $cmd->{arguments}->{fd};\n            }\n\n            my $qmpcmd;\n\n            if ($qga) {\n\n                $qmpcmd = to_json({\n                    execute => 'guest-sync-delimited',\n                    arguments => { id => int($cmd->{id}) },\n                })\n                    . \"\\n\"\n                    . to_json({ execute => $cmd->{execute}, arguments => $cmd->{arguments} })\n                    . \"\\n\";\n\n            } else {\n\n                $qmpcmd = to_json({\n                    execute => $cmd->{execute},\n                    arguments => $cmd->{arguments},\n                    id => $cmd->{id},\n                });\n            }\n\n            if ($fd >= 0) {\n                my $ret = PVE::IPCC::sendfd(fileno($fh), $fd, $qmpcmd);\n                die \"sendfd failed\" if $ret < 0;\n            } else {\n                $self->{mux}->write($fh, $qmpcmd);\n            }\n        };\n        if (my $err = $@) {\n            $queue_info->{error} = $err;\n        } else {\n            $running++;\n        }\n    }\n\n    $self->{mux}->endloop() if !$running;\n\n    return $running;\n};\n\n# execute all queued command\n\nsub queue_execute {\n    my ($self, $timeout, $noerr) = @_;\n\n    $timeout = 3 if !$timeout;\n\n    # open all necessary connections\n    foreach my $sname (keys %{ $self->{queue_info} }) {\n        my $queue_info = $self->{queue_info}->{$sname};\n        next if !scalar(@{ $queue_info->{cmds} }); # no commands\n\n        $queue_info->{error} = undef;\n        $queue_info->{current} = undef;\n\n        eval {\n            &$open_connection($self, $queue_info, $timeout);\n\n            if ($queue_info->{peer}->{type} ne 'qga') {\n                my $cap_cmd = { execute => 'qmp_capabilities', arguments => {} };\n                unshift @{ $queue_info->{cmds} }, $cap_cmd;\n            }\n        };\n        if (my $err = $@) {\n            $queue_info->{error} = $err;\n        }\n    }\n\n    my $running;\n\n    for (;;) {\n\n        $running = &$check_queue($self);\n\n        last if !$running;\n\n        $self->{mux}->loop;\n    }\n\n    # make sure we close everything\n    my $errors = '';\n    foreach my $sname (keys %{ $self->{queue_info} }) {\n        my $queue_info = $self->{queue_info}->{$sname};\n        &$close_connection($self, $queue_info);\n        if ($queue_info->{error}) {\n            if ($noerr) {\n                warn $queue_info->{error} if $noerr < 2;\n            } else {\n                $errors .= $queue_info->{error};\n            }\n        }\n    }\n\n    $self->{queue_info} = $self->{queue_lookup} = {};\n\n    die $errors if $errors;\n}\n\nsub mux_close {\n    my ($self, $mux, $fh) = @_;\n\n    my $queue_info = &$lookup_queue_info($self, $fh, 1);\n    return if !$queue_info;\n\n    $queue_info->{error} = \"client closed connection\\n\"\n        if !$queue_info->{error};\n}\n\n# mux_input is called when input is available on one of the descriptors.\nsub mux_input {\n    my ($self, $mux, $fh, $input) = @_;\n\n    my $queue_info = &$lookup_queue_info($self, $fh);\n    return if !$queue_info;\n\n    my $sname = $queue_info->{sname};\n    my ($id, $peer_name) = $queue_info->{peer}->@{qw(id name)};\n    my $qga = $queue_info->{peer}->{type} eq 'qga';\n\n    my $curcmd = $queue_info->{current};\n    die \"unable to lookup current command for $peer_name ($sname)\\n\" if !$curcmd;\n\n    my $raw;\n\n    if ($qga) {\n        return if $$input !~ s/^.*\\xff([^\\n]+}\\r?\\n[^\\n]+})\\r?\\n(.*)$/$2/so;\n        $raw = $1;\n    } else {\n        return if $$input !~ s/^(.*})\\r?\\n(.*)$/$2/so;\n        $raw = $1;\n    }\n\n    eval {\n        my @jsons = split(\"\\n\", $raw);\n\n        if ($qga) {\n\n            die \"response is not complete\" if @jsons != 2;\n\n            my $obj = from_json($jsons[0]);\n\n            my $cmdid = $obj->{'return'};\n            die \"received response without command id\\n\" if !$cmdid;\n\n            # skip results from previous commands\n            return if $cmdid < $curcmd->{id};\n\n            if ($curcmd->{id} ne $cmdid) {\n                die \"got wrong command id '$cmdid' (expected $curcmd->{id})\\n\";\n            }\n\n            delete $queue_info->{current};\n\n            $obj = from_json($jsons[1]);\n\n            if (my $callback = $curcmd->{callback}) {\n                &$callback($id, $obj);\n            }\n\n            return;\n        }\n\n        foreach my $json (@jsons) {\n            my $obj = from_json($json);\n            next if defined($obj->{QMP}); # skip monitor greeting\n\n            if (exists($obj->{error}->{desc})) {\n                my $desc = $obj->{error}->{desc};\n                chomp $desc;\n                die \"$desc\\n\" if $desc !~ m/Connection can not be completed immediately/;\n                next;\n            }\n\n            if (defined($obj->{event})) {\n                if (my $eventcb = $self->{eventcb}) {\n                    &$eventcb($obj);\n                }\n                next;\n            }\n\n            my $cmdid = $obj->{id};\n            die \"received response without command id\\n\" if !$cmdid;\n\n            if ($curcmd->{id} ne $cmdid) {\n                die \"got wrong command id '$cmdid' (expected $curcmd->{id})\\n\";\n            }\n\n            delete $queue_info->{current};\n\n            if (my $callback = $curcmd->{callback}) {\n                &$callback($id, $obj);\n            }\n        }\n    };\n    if (my $err = $@) {\n        $queue_info->{error} = $err;\n    }\n\n    &$check_queue($self);\n}\n\nsub mux_timeout {\n    my ($self, $mux, $fh) = @_;\n\n    if (my $queue_info = &$lookup_queue_info($self, $fh)) {\n        $queue_info->{error} = \"got timeout\\n\";\n        $queue_info->{'error-is-timeout'} = 1;\n        $self->{mux}->inbuffer($fh, ''); # clear to avoid warnings\n    }\n\n    &$check_queue($self);\n}\n\nsub mux_eof {\n    my ($self, $mux, $fh, $input) = @_;\n\n    my $queue_info = &$lookup_queue_info($self, $fh);\n    return if !$queue_info;\n\n    my $sname = $queue_info->{sname};\n    my ($id, $peer_name) = $queue_info->{peer}->@{qw(id name)};\n    my $qga = $queue_info->{peer}->{type} eq 'qga';\n\n    my $curcmd = $queue_info->{current};\n    die \"unable to lookup current command for $peer_name ($sname)\\n\" if !$curcmd;\n\n    if ($qga && $qga_allow_close_cmds->{ $curcmd->{execute} }) {\n\n        return if $$input !~ s/^.*\\xff([^\\n]+})\\r?\\n(.*)$/$2/so;\n\n        my $raw = $1;\n\n        eval {\n            my $obj = from_json($raw);\n\n            my $cmdid = $obj->{'return'};\n            die \"received response without command id\\n\" if !$cmdid;\n\n            delete $queue_info->{current};\n\n            if (my $callback = $curcmd->{callback}) {\n                &$callback($id, undef);\n            }\n        };\n        if (my $err = $@) {\n            $queue_info->{error} = $err;\n        }\n\n        &$close_connection($self, $queue_info);\n\n        if (scalar(@{ $queue_info->{cmds} }) && !$queue_info->{error}) {\n            $queue_info->{error} = \"Got EOF but command queue is not empty.\\n\";\n        }\n    }\n}\n\nsub DESTROY {\n    my ($self) = @_;\n\n    foreach my $sname (keys %{ $self->{queue_info} }) {\n        my $queue_info = $self->{queue_info}->{$sname};\n        $close_connection->($self, $queue_info);\n    }\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuConfig/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES=NoWrite.pm\n\n.PHONY: install\ninstall: $(SOURCES)\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/QemuConfig/$$i; done\n"
  },
  {
    "path": "src/PVE/QemuConfig/NoWrite.pm",
    "content": "package PVE::QemuConfig::NoWrite;\n\nuse strict;\nuse warnings;\n\nuse PVE::RESTEnvironment qw(log_warn);\n\nuse base qw(PVE::QemuConfig);\n\nsub mark_config {\n    my ($class, $conf) = @_;\n\n    bless($conf, $class);\n}\n\nsub write_config {\n    my ($class, $vmid, $conf) = @_;\n\n    die(\"refusing to write temporary configuration\\n\");\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuConfig.pm",
    "content": "package PVE::QemuConfig;\n\nuse strict;\nuse warnings;\n\nuse Scalar::Util qw(blessed);\n\nuse PVE::AbstractConfig;\nuse PVE::INotify;\nuse PVE::JSONSchema;\nuse PVE::QemuMigrate::Helpers;\nuse PVE::QemuServer::Agent;\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::CPUConfig;\nuse PVE::QemuServer::Drive;\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Monitor qw(mon_cmd);\nuse PVE::QemuServer;\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Memory qw(get_current_memory);\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Storage;\nuse PVE::Tools;\nuse PVE::Format qw(render_bytes render_duration);\n\nuse base qw(PVE::AbstractConfig);\n\nmy $nodename = PVE::INotify::nodename();\n\nmkdir \"/etc/pve/nodes/$nodename\";\nmkdir \"/etc/pve/nodes/$nodename/qemu-server\";\n\nmy $lock_dir = \"/var/lock/qemu-server\";\nmkdir $lock_dir;\n\nsub assert_config_exists_on_node {\n    my ($vmid, $node) = @_;\n\n    $node //= $nodename;\n\n    my $filename = __PACKAGE__->config_file($vmid, $node);\n    my $exists = -f $filename;\n\n    my $type = guest_type();\n    die \"unable to find configuration file for $type $vmid on node '$node'\\n\"\n        if !$exists;\n}\n\n# BEGIN implemented abstract methods from PVE::AbstractConfig\n\nsub guest_type {\n    return \"VM\";\n}\n\nsub __config_max_unused_disks {\n    my ($class) = @_;\n\n    return $PVE::QemuServer::Drive::MAX_UNUSED_DISKS;\n}\n\nsub config_file_lock {\n    my ($class, $vmid) = @_;\n\n    return \"$lock_dir/lock-$vmid.conf\";\n}\n\nsub cfs_config_path {\n    my ($class, $vmid, $node) = @_;\n\n    $node = $nodename if !$node;\n    return \"nodes/$node/qemu-server/$vmid.conf\";\n}\n\nsub has_feature {\n    my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;\n\n    my $err;\n    $class->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n            return if $backup_only && defined($drive->{backup}) && !$drive->{backup};\n            my $volid = $drive->{file};\n            $err = 1\n                if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname,\n                    $running);\n        },\n    );\n\n    return $err ? 0 : 1;\n}\n\nsub valid_volume_keys {\n    my ($class, $reverse) = @_;\n\n    my @keys = PVE::QemuServer::Drive::valid_drive_names();\n\n    return $reverse ? reverse @keys : @keys;\n}\n\n# FIXME: adapt parse_drive to use $noerr for better error messages\nsub parse_volume {\n    my ($class, $key, $volume_string, $noerr) = @_;\n\n    my $volume;\n    if ($key eq 'vmstate') {\n        eval { PVE::JSONSchema::check_format('pve-volume-id', $volume_string) };\n        if (my $err = $@) {\n            return if $noerr;\n            die $err;\n        }\n        $volume = { 'file' => $volume_string };\n    } else {\n        $volume = PVE::QemuServer::Drive::parse_drive($key, $volume_string);\n    }\n\n    die \"unable to parse volume\\n\" if !defined($volume) && !$noerr;\n\n    return $volume;\n}\n\nsub print_volume {\n    my ($class, $key, $volume) = @_;\n\n    return PVE::QemuServer::Drive::print_drive($volume);\n}\n\nsub volid_key {\n    my ($class) = @_;\n\n    return 'file';\n}\n\nsub get_replicatable_volumes {\n    my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;\n\n    my $volhash = {};\n\n    my $test_volid = sub {\n        my ($volid, $attr) = @_;\n\n        return if $attr->{cdrom};\n\n        return if !$cleanup && !$attr->{replicate};\n\n        if ($volid =~ m|^/|) {\n            return if !$attr->{replicate};\n            return if $cleanup || $noerr;\n            die \"unable to replicate local file/device '$volid'\\n\";\n        }\n\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);\n        return if !$storeid;\n\n        my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n        return if $scfg->{shared};\n\n        my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);\n        return if !$owner || ($owner != $vmid);\n\n        if ($vtype ne 'images') {\n            return if $cleanup || $noerr;\n            die \"unable to replicate volume '$volid', type '$vtype'\\n\";\n        }\n\n        if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {\n            return if $cleanup || $noerr;\n            die \"missing replicate feature on volume '$volid'\\n\";\n        }\n\n        $volhash->{$volid} = 1;\n    };\n\n    PVE::QemuServer::foreach_volid($conf, $test_volid);\n\n    return $volhash;\n}\n\nsub get_backup_volumes {\n    my ($class, $conf) = @_;\n\n    my $return_volumes = [];\n\n    my $test_volume = sub {\n        my ($key, $drive) = @_;\n\n        return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n        my $included = $drive->{backup} // 1;\n        my $reason = \"backup=\";\n        $reason .= defined($drive->{backup}) ? 'no' : 'yes';\n\n        if ($key =~ m/^efidisk/ && (!defined($conf->{bios}) || $conf->{bios} ne 'ovmf')) {\n            $included = 0;\n            $reason = \"efidisk but no OVMF BIOS\";\n        }\n\n        push @$return_volumes,\n            {\n                key => $key,\n                included => $included,\n                reason => $reason,\n                volume_config => $drive,\n            };\n    };\n\n    PVE::QemuConfig->foreach_volume($conf, $test_volume);\n\n    return $return_volumes;\n}\n\nsub __snapshot_assert_no_blockers {\n    my ($class, $vmconf, $save_vmstate) = @_;\n    PVE::QemuMigrate::Helpers::check_non_migratable_resources($vmconf, $save_vmstate, 0);\n}\n\nsub __snapshot_save_vmstate {\n    my ($class, $vmid, $conf, $snapname, $storecfg, $statestorage, $suspend) = @_;\n\n    # use given storage or search for one from the config\n    my $target = $statestorage;\n\n    if (!$target) {\n        $target = find_vmstate_storage($conf, $storecfg);\n    }\n\n    my $mem_size = get_current_memory($conf->{memory});\n    my $driver_state_size = 500; # assume 500MB is enough to safe all driver state;\n    # our savevm-start does live-save of the memory until the space left in the\n    # volume is just enough for the remaining memory content + internal state\n    # then it stops the vm and copies the rest so we reserve twice the\n    # memory content + state to minimize vm downtime\n    my $size = $mem_size * 2 + $driver_state_size;\n    my $scfg = PVE::Storage::storage_config($storecfg, $target);\n\n    my $name = \"vm-$vmid-state-$snapname\";\n    $name .= \".raw\" if $scfg->{path}; # add filename extension for file base storage\n\n    my $statefile =\n        PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size * 1024);\n    my $runningmachine = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n\n    # get current QEMU -cpu argument to ensure consistency of custom CPU models\n    my $pid = PVE::QemuServer::Helpers::vm_running_locally($vmid)\n        or die \"cannot obtain PID for VM $vmid!\\n\";\n    my $runningcpu = PVE::QemuServer::CPUConfig::get_cpu_from_running_vm($pid);\n\n    my $nets_host_mtu = PVE::QemuServer::Network::get_nets_host_mtu($vmid, $conf);\n\n    if (!$suspend) {\n        $conf = $conf->{snapshots}->{$snapname};\n    }\n\n    $conf->{vmstate} = $statefile;\n    $conf->{runningmachine} = $runningmachine;\n    $conf->{runningcpu} = $runningcpu;\n    $conf->{'running-nets-host-mtu'} = $nets_host_mtu;\n\n    return $statefile;\n}\n\nsub __snapshot_activate_storages {\n    my ($class, $conf, $include_vmstate) = @_;\n\n    my $storecfg = PVE::Storage::config();\n    my $opts = $include_vmstate ? { 'extra_keys' => ['vmstate'] } : {};\n    my $storage_hash = {};\n\n    $class->foreach_volume_full(\n        $conf,\n        $opts,\n        sub {\n            my ($key, $drive) = @_;\n\n            return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n            my ($storeid) = PVE::Storage::parse_volume_id($drive->{file});\n            $storage_hash->{$storeid} = 1;\n        },\n    );\n\n    PVE::Storage::activate_storage_list($storecfg, [sort keys $storage_hash->%*]);\n}\n\nsub __snapshot_check_running {\n    my ($class, $vmid) = @_;\n    return PVE::QemuServer::Helpers::vm_running_locally($vmid);\n}\n\nsub __snapshot_check_freeze_needed {\n    my ($class, $vmid, $config, $save_vmstate) = @_;\n\n    my $running = $class->__snapshot_check_running($vmid);\n    if (!$save_vmstate) {\n        return (\n            $running,\n            $running\n                && PVE::QemuServer::Agent::should_fs_freeze($config)\n                && PVE::QemuServer::Agent::qga_check_running($vmid),\n        );\n    } else {\n        return ($running, 0);\n    }\n}\n\nsub __snapshot_freeze {\n    my ($class, $vmid, $unfreeze) = @_;\n\n    if ($unfreeze) {\n        eval { PVE::QemuServer::Agent::guest_fsthaw($vmid); };\n        warn \"guest-fsfreeze-thaw problems - $@\" if $@;\n    } else {\n        eval { PVE::QemuServer::Agent::guest_fsfreeze($vmid); };\n        warn $@ if $@;\n    }\n}\n\nsub __snapshot_create_vol_snapshots_hook {\n    my ($class, $vmid, $snap, $running, $hook) = @_;\n\n    if ($running) {\n        my $storecfg = PVE::Storage::config();\n\n        if ($hook eq \"before\") {\n            if ($snap->{vmstate}) {\n                my $path = PVE::Storage::path($storecfg, $snap->{vmstate});\n                PVE::Storage::activate_volumes($storecfg, [$snap->{vmstate}]);\n                my $state_storage_id = PVE::Storage::parse_volume_id($snap->{vmstate});\n\n                PVE::QemuMigrate::Helpers::set_migration_caps($vmid, 1);\n                mon_cmd($vmid, \"savevm-start\", statefile => $path);\n                print \"saving VM state and RAM using storage '$state_storage_id'\\n\";\n                my $render_state = sub {\n                    my ($stat) = @_;\n                    my $b = render_bytes($stat->{bytes});\n                    my $t = render_duration($stat->{'total-time'} / 1000);\n                    return ($b, $t);\n                };\n                my $round = 0;\n                for (;;) {\n                    $round++;\n                    my $stat = mon_cmd($vmid, \"query-savevm\");\n                    if (!$stat->{status}) {\n                        die \"savevm not active\\n\";\n                    } elsif ($stat->{status} eq 'active') {\n                        if ($round < 60 || $round % 10 == 0) {\n                            my ($b, $t) = $render_state->($stat);\n                            print \"$b in $t\\n\";\n                        }\n                        print \"reducing reporting rate to every 10s\\n\" if $round == 60;\n                        sleep(1);\n                        next;\n                    } elsif ($stat->{status} eq 'completed') {\n                        my ($b, $t) = $render_state->($stat);\n                        print \"completed saving the VM state in $t, saved $b\\n\";\n                        last;\n                    } elsif ($stat->{status} eq 'failed') {\n                        my $err = $stat->{error} || 'unknown error';\n                        die \"unable to save VM state and RAM - $err\\n\";\n                    } else {\n                        die \"query-savevm returned unexpected status '$stat->{status}'\\n\";\n                    }\n                }\n            } else {\n                mon_cmd($vmid, \"savevm-start\");\n            }\n        } elsif ($hook eq \"after\") {\n            eval {\n                mon_cmd($vmid, \"savevm-end\");\n                PVE::Storage::deactivate_volumes($storecfg, [$snap->{vmstate}])\n                    if $snap->{vmstate};\n            };\n            warn $@ if $@;\n        } elsif ($hook eq \"after-freeze\") {\n            # savevm-end is async, we need to wait\n            for (;;) {\n                my $stat = mon_cmd($vmid, \"query-savevm\");\n                if (!$stat->{bytes}) {\n                    last;\n                } else {\n                    print \"savevm not yet finished\\n\";\n                    sleep(1);\n                    next;\n                }\n            }\n        }\n    }\n}\n\nsub __snapshot_create_vol_snapshot {\n    my ($class, $vmid, $ds, $drive, $snapname) = @_;\n\n    return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n    my $volid = $drive->{file};\n    my $device = \"drive-$ds\";\n    my $storecfg = PVE::Storage::config();\n\n    print \"snapshotting '$device' ($drive->{file})\\n\";\n\n    PVE::QemuServer::qemu_volume_snapshot($vmid, $device, $storecfg, $drive, $snapname);\n}\n\nsub __snapshot_delete_remove_drive {\n    my ($class, $snap, $remove_drive) = @_;\n\n    if ($remove_drive eq 'vmstate') {\n        delete $snap->{$remove_drive};\n    } else {\n        my $drive = PVE::QemuServer::Drive::parse_drive($remove_drive, $snap->{$remove_drive});\n        return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n        my $volid = $drive->{file};\n        delete $snap->{$remove_drive};\n        $class->add_unused_volume($snap, $volid);\n    }\n}\n\nsub __snapshot_delete_vmstate_file {\n    my ($class, $snap, $force) = @_;\n\n    my $storecfg = PVE::Storage::config();\n\n    eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); };\n    if (my $err = $@) {\n        die $err if !$force;\n        warn $err;\n    }\n}\n\nsub __snapshot_delete_vol_snapshot {\n    my ($class, $vmid, $ds, $drive, $snapname, $unused) = @_;\n\n    return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n    my $storecfg = PVE::Storage::config();\n    my $volid = $drive->{file};\n\n    PVE::QemuServer::qemu_volume_snapshot_delete($vmid, $storecfg, $drive, $snapname);\n\n    push @$unused, $volid;\n}\n\nsub __snapshot_rollback_hook {\n    my ($class, $vmid, $conf, $snap, $prepare, $data) = @_;\n\n    if ($prepare) {\n        # we save the machine of the current config\n        $data->{oldmachine} = $conf->{machine};\n    } else {\n        # if we have a 'runningmachine' entry in the snapshot we use that\n        # for the forcemachine parameter, else we use the old logic\n        if (defined($conf->{runningmachine})) {\n            $data->{forcemachine} = $conf->{runningmachine};\n            delete $conf->{runningmachine};\n\n            # runningcpu is newer than runningmachine, so assume it only exists\n            # here, if at all\n            $data->{forcecpu} = delete $conf->{runningcpu}\n                if defined($conf->{runningcpu});\n        } else {\n            # Note: old code did not store 'machine', so we try to be smart\n            # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).\n            my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine});\n            $data->{forcemachine} = $machine_conf->{type} || 'pc-i440fx-1.4';\n\n            # we remove the 'machine' configuration if not explicitly specified\n            # in the original config.\n            delete $conf->{machine} if $snap->{vmstate} && !defined($data->{oldmachine});\n        }\n\n        if ($conf->{vmgenid}) {\n            # tell the VM that it's another generation, so it can react\n            # appropriately, e.g. dirty-mark copies of distributed databases or\n            # re-initializing its random number generator\n            $conf->{vmgenid} = PVE::QemuServer::generate_uuid();\n        }\n\n        $data->{'nets-host-mtu'} = delete($conf->{'running-nets-host-mtu'});\n    }\n\n    return;\n}\n\nsub __snapshot_rollback_vol_possible {\n    my ($class, $drive, $snapname, $blockers) = @_;\n\n    return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n    my $storecfg = PVE::Storage::config();\n    my $volid = $drive->{file};\n\n    PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname, $blockers);\n}\n\nsub __snapshot_rollback_vol_rollback {\n    my ($class, $drive, $snapname) = @_;\n\n    return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n    my $storecfg = PVE::Storage::config();\n    PVE::Storage::volume_snapshot_rollback($storecfg, $drive->{file}, $snapname);\n}\n\nsub __snapshot_rollback_vm_stop {\n    my ($class, $vmid) = @_;\n\n    my $storecfg = PVE::Storage::config();\n    PVE::QemuServer::vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef);\n}\n\nsub __snapshot_rollback_vm_start {\n    my ($class, $vmid, $vmstate, $data) = @_;\n\n    my $storecfg = PVE::Storage::config();\n    my $params = {\n        statefile => $vmstate,\n        forcemachine => $data->{forcemachine},\n        forcecpu => $data->{forcecpu},\n        'nets-host-mtu' => $data->{'nets-host-mtu'},\n    };\n    PVE::QemuServer::vm_start($storecfg, $vmid, $params);\n}\n\nsub __snapshot_rollback_get_unused {\n    my ($class, $conf, $snap) = @_;\n\n    my $unused = [];\n\n    $class->foreach_volume(\n        $conf,\n        sub {\n            my ($vs, $volume) = @_;\n\n            return if PVE::QemuServer::Drive::drive_is_cdrom($volume, 1);\n\n            my $found = 0;\n            my $volid = $volume->{file};\n\n            $class->foreach_volume(\n                $snap,\n                sub {\n                    my ($ds, $drive) = @_;\n\n                    return if $found;\n                    return if PVE::QemuServer::Drive::drive_is_cdrom($drive, 1);\n\n                    $found = 1\n                        if ($drive->{file} && $drive->{file} eq $volid);\n                },\n            );\n\n            push @$unused, $volid if !$found;\n        },\n    );\n\n    return $unused;\n}\n\nsub add_unused_volume {\n    my ($class, $config, $volid) = @_;\n\n    if ($volid =~ m/vm-\\d+-cloudinit/) {\n        print \"found unused cloudinit disk '$volid', removing it\\n\";\n        my $storecfg = PVE::Storage::config();\n        PVE::Storage::vdisk_free($storecfg, $volid);\n        return undef;\n    } else {\n        return $class->SUPER::add_unused_volume($config, $volid);\n    }\n}\n\nsub load_current_config {\n    my ($class, $vmid, $current) = @_;\n\n    my $conf = $class->SUPER::load_current_config($vmid, $current);\n    delete $conf->{'special-sections'};\n    return $conf;\n}\n\nsub get_derived_property {\n    my ($class, $conf, $name) = @_;\n\n    if ($name eq 'max-cpu') {\n        my $sockets = $conf->{sockets} || PVE::QemuServer::get_default_property_value('sockets');\n        my $cores = $conf->{cores} || PVE::QemuServer::get_default_property_value('cores');\n        return $conf->{vcpus} || ($sockets * $cores);\n    } elsif ($name eq 'max-memory') { # current usage maximum, not maximum hotpluggable\n        return get_current_memory($conf->{memory}) * 1024 * 1024;\n    } else {\n        die \"unknown derived property - $name\\n\";\n    }\n}\n\nsub write_config {\n    my ($class, $vmid, $conf) = @_;\n\n    # Dispatch to class the object was blessed with if caller invoked the method via the\n    # 'PVE::QemuConfig' class name explicitly. This is hack, but the code currently doesn't\n    # generally use blessed config objects. Safeguard against infinite recursion.\n    if (blessed($conf) && !blessed($class)) {\n        return $conf->write_config($vmid, $conf);\n    }\n\n    return $class->SUPER::write_config($vmid, $conf);\n}\n\n# END implemented abstract methods from PVE::AbstractConfig\n\nsub has_cloudinit {\n    my ($class, $conf, $skip) = @_;\n\n    my $found;\n\n    $class->foreach_volume(\n        $conf,\n        sub {\n            my ($key, $volume) = @_;\n\n            return if ($skip && $skip eq $key) || $found;\n            $found = $key if PVE::QemuServer::Drive::drive_is_cloudinit($volume);\n        },\n    );\n\n    return $found;\n}\n\n# Caller is expected to deal with volumes from an already existing 'fleecing' special section in the\n# configuration first.\nsub record_fleecing_images {\n    my ($vmid, $volids) = @_;\n\n    return if scalar($volids->@*) == 0;\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n            my $conf = PVE::QemuConfig->load_config($vmid);\n            $conf->{'special-sections'}->{fleecing}->{'fleecing-images'} =\n                join(',', $volids->@*);\n            PVE::QemuConfig->write_config($vmid, $conf);\n        },\n    );\n}\n\n# Will also cancel a running backup job inside QEMU. Not doing so can lead to a deadlock when\n# attempting to detach the fleecing image.\nsub cleanup_fleecing_images {\n    my ($vmid, $storecfg, $log_func) = @_;\n\n    if (!$log_func) {\n        $log_func = sub {\n            my ($level, $line) = @_;\n            chomp($line);\n            if ($level eq 'info') {\n                print \"$line\\n\";\n            } else {\n                log_warn($line);\n            }\n        };\n    }\n\n    my $volids = [];\n    my $failed = [];\n\n    # cancel left-over backup job and detach any left-over images from a running VM\n    if (PVE::QemuServer::Helpers::vm_running_locally($vmid)) {\n        eval {\n            if (my $status = mon_cmd($vmid, 'query-backup')) {\n                if ($status->{status} && $status->{status} eq 'active') {\n                    $log_func->(\n                        'warn',\n                        \"left-over backup job still running inside QEMU - canceling now\",\n                    );\n                    mon_cmd($vmid, 'backup-cancel');\n                }\n            }\n        };\n        $log_func->('warn', \"checking/canceling old backup job failed - $@\") if $@;\n\n        PVE::QemuServer::Blockdev::detach_fleecing_block_nodes($vmid, $log_func);\n    }\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n            my $conf = PVE::QemuConfig->load_config($vmid);\n            my $special = $conf->{'special-sections'};\n            if (my $fleecing = $special->{fleecing}) {\n                $volids = [PVE::Tools::split_list($fleecing->{'fleecing-images'})];\n                delete $fleecing->{'fleecing-images'};\n                delete $special->{fleecing} if !scalar(keys $fleecing->%*);\n                PVE::QemuConfig->write_config($vmid, $conf);\n            }\n        },\n    );\n\n    for my $volid ($volids->@*) {\n        $log_func->('info', \"removing (old) fleecing image '$volid'\");\n        eval { PVE::Storage::vdisk_free($storecfg, $volid); };\n        if (my $err = $@) {\n            $log_func->('warn', \"error removing fleecing image '$volid' - $err\");\n            push $failed->@*, $volid;\n        }\n    }\n\n    record_fleecing_images($vmid, $failed);\n}\n\nsub foreach_storage_used_by_vm {\n    my ($conf, $func) = @_;\n\n    my $sidhash = {};\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n            return if PVE::QemuServer::Drive::drive_is_cdrom($drive);\n\n            my $volid = $drive->{file};\n\n            my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n            $sidhash->{$sid} = $sid if $sid;\n        },\n    );\n\n    foreach my $sid (sort keys %$sidhash) {\n        &$func($sid);\n    }\n}\n\n# NOTE: if this logic changes, please update docs & possibly gui logic\nsub find_vmstate_storage {\n    my ($conf, $storecfg) = @_;\n\n    # first, return storage from conf if set\n    return $conf->{vmstatestorage} if $conf->{vmstatestorage};\n\n    my ($target, $shared, $local);\n\n    foreach_storage_used_by_vm(\n        $conf,\n        sub {\n            my ($sid) = @_;\n            my $scfg = PVE::Storage::storage_config($storecfg, $sid);\n            my $dst = $scfg->{shared} ? \\$shared : \\$local;\n            $$dst = $sid if !$$dst || $scfg->{path}; # prefer file based storage\n        },\n    );\n\n    # second, use shared storage where VM has at least one disk\n    # third, use local storage where VM has at least one disk\n    # fall back to local storage\n    $target = $shared // $local // 'local';\n\n    return $target;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuMigrate/Helpers.pm",
    "content": "package PVE::QemuMigrate::Helpers;\n\nuse strict;\nuse warnings;\n\nuse JSON;\n\nuse PVE::Cluster;\nuse PVE::JSONSchema qw(parse_property_string);\nuse PVE::Mapping::Dir;\nuse PVE::Mapping::PCI;\nuse PVE::Mapping::USB;\n\nuse PVE::QemuServer::Monitor qw(mon_cmd);\nuse PVE::QemuServer::Virtiofs;\n\nsub check_non_migratable_resources {\n    my ($conf, $state, $noerr) = @_;\n\n    my @blockers = ();\n    if ($state) {\n        push @blockers, \"amd-sev\" if $conf->{\"amd-sev\"};\n        push @blockers, \"intel-tdx\" if $conf->{\"intel-tdx\"};\n        push @blockers, \"virtiofs\" if PVE::QemuServer::Virtiofs::virtiofs_enabled($conf);\n    }\n\n    if (scalar(@blockers) && !$noerr) {\n        die \"Cannot live-migrate, snapshot (with RAM), or hibernate a VM with: \"\n            . join(', ', @blockers) . \"\\n\";\n    }\n\n    return @blockers;\n}\n\n# test if VM uses local resources (to prevent migration)\nsub check_local_resources {\n    my ($conf, $state, $noerr) = @_;\n\n    my @loc_res = ();\n    my $mapped_res = {};\n\n    my @non_migratable_resources = check_non_migratable_resources($conf, $state, $noerr);\n    push(@loc_res, @non_migratable_resources);\n\n    my $nodelist = PVE::Cluster::get_nodelist();\n    my $pci_map = PVE::Mapping::PCI::config();\n    my $usb_map = PVE::Mapping::USB::config();\n    my $dir_map = PVE::Mapping::Dir::config();\n\n    my $missing_mappings_by_node = { map { $_ => [] } @$nodelist };\n\n    my $add_missing_mapping = sub {\n        my ($type, $key, $id) = @_;\n        for my $node (@$nodelist) {\n            my $entry;\n            if ($type eq 'pci') {\n                $entry = PVE::Mapping::PCI::get_node_mapping($pci_map, $id, $node);\n            } elsif ($type eq 'usb') {\n                $entry = PVE::Mapping::USB::get_node_mapping($usb_map, $id, $node);\n            } elsif ($type eq 'dir') {\n                $entry = PVE::Mapping::Dir::get_node_mapping($dir_map, $id, $node);\n            }\n            if (!scalar($entry->@*)) {\n                push @{ $missing_mappings_by_node->{$node} }, $key;\n            }\n        }\n    };\n\n    push @loc_res, \"hostusb\" if $conf->{hostusb}; # old syntax\n    push @loc_res, \"hostpci\" if $conf->{hostpci}; # old syntax\n\n    push @loc_res, \"ivshmem\" if $conf->{ivshmem};\n\n    foreach my $k (keys %$conf) {\n        if ($k =~ m/^usb/) {\n            my $entry = parse_property_string('pve-qm-usb', $conf->{$k});\n            next if $entry->{host} && $entry->{host} =~ m/^spice$/i;\n            if (my $name = $entry->{mapping}) {\n                $add_missing_mapping->('usb', $k, $name);\n                $mapped_res->{$k} = { name => $name };\n            }\n        }\n        if ($k =~ m/^hostpci/) {\n            my $entry = parse_property_string('pve-qm-hostpci', $conf->{$k});\n            if (my $name = $entry->{mapping}) {\n                $add_missing_mapping->('pci', $k, $name);\n                my $mapped_device = { name => $name };\n                $mapped_res->{$k} = $mapped_device;\n\n                if ($pci_map->{ids}->{$name}->{'live-migration-capable'}) {\n                    $mapped_device->{'live-migration'} = 1;\n                    # don't add mapped device with live migration as blocker\n                    next;\n                }\n\n                # don't add mapped devices as blocker for offline migration but still iterate over\n                # all mappings above to collect on which nodes they are available.\n                next if !$state;\n            }\n        }\n        if ($k =~ m/^virtiofs/) {\n            my $entry = parse_property_string('pve-qm-virtiofs', $conf->{$k});\n            $add_missing_mapping->('dir', $k, $entry->{dirid});\n            $mapped_res->{$k} = { name => $entry->{dirid} };\n        }\n        # sockets are safe: they will recreated be on the target side post-migrate\n        next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket');\n        push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel|virtiofs)\\d+$/;\n    }\n\n    die \"VM uses local resources\\n\" if scalar @loc_res && !$noerr;\n\n    return wantarray ? (\\@loc_res, $mapped_res, $missing_mappings_by_node) : \\@loc_res;\n}\n\nsub set_migration_caps {\n    my ($vmid, $savevm) = @_;\n\n    my $qemu_support = eval { mon_cmd($vmid, \"query-proxmox-support\") };\n\n    my $bitmap_prop = $savevm ? 'pbs-dirty-bitmap-savevm' : 'pbs-dirty-bitmap-migration';\n    my $dirty_bitmaps = $qemu_support->{$bitmap_prop} ? 1 : 0;\n\n    my $cap_ref = [];\n\n    my $enabled_cap = {\n        \"auto-converge\" => 1,\n        \"xbzrle\" => 1,\n        \"dirty-bitmaps\" => $dirty_bitmaps,\n    };\n\n    my $supported_capabilities = mon_cmd($vmid, \"query-migrate-capabilities\");\n\n    for my $supported_capability (@$supported_capabilities) {\n        push @$cap_ref,\n            {\n                capability => $supported_capability->{capability},\n                state => $enabled_cap->{ $supported_capability->{capability} }\n                ? JSON::true\n                : JSON::false,\n            };\n    }\n\n    mon_cmd($vmid, \"migrate-set-capabilities\", capabilities => $cap_ref);\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuMigrate/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES=Helpers.pm\n\n.PHONY: install\ninstall: $(SOURCES)\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/QemuMigrate/$$i; done\n"
  },
  {
    "path": "src/PVE/QemuMigrate.pm",
    "content": "package PVE::QemuMigrate;\n\nuse strict;\nuse warnings;\n\nuse IO::File;\nuse IPC::Open2;\nuse Storable qw(dclone);\nuse Time::HiRes qw( usleep );\n\nuse PVE::AccessControl;\nuse PVE::Cluster;\nuse PVE::Format qw(render_bytes);\nuse PVE::Firewall::Helpers;\nuse PVE::GuestHelpers qw(safe_boolean_ne safe_string_ne);\nuse PVE::INotify;\nuse PVE::JSONSchema;\nuse PVE::RPCEnvironment;\nuse PVE::Replication;\nuse PVE::ReplicationConfig;\nuse PVE::ReplicationState;\nuse PVE::Storage::Plugin;\nuse PVE::Storage;\nuse PVE::StorageTunnel;\nuse PVE::Tools;\nuse PVE::Tunnel;\n\nuse PVE::QemuConfig;\nuse PVE::QemuMigrate::Helpers;\nuse PVE::QemuServer::Agent;\nuse PVE::QemuServer::BlockJob;\nuse PVE::QemuServer::CPUConfig;\nuse PVE::QemuServer::Drive qw(checked_volume_format);\nuse PVE::QemuServer::Helpers qw(min_version);\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);\nuse PVE::QemuServer::Memory qw(get_current_memory);\nuse PVE::QemuServer::Network;\nuse PVE::QemuServer::QMPHelpers;\nuse PVE::QemuServer::DBusVMState;\nuse PVE::QemuServer;\n\nuse PVE::AbstractMigrate;\nuse base qw(PVE::AbstractMigrate);\n\n# compared against remote end's minimum version\nour $WS_TUNNEL_VERSION = 2;\n\nsub fork_tunnel {\n    my ($self, $ssh_forward_info) = @_;\n\n    my $cmd = ['/usr/sbin/qm', 'mtunnel'];\n    my $log = sub {\n        my ($level, $msg) = @_;\n        $self->log($level, $msg);\n    };\n\n    return PVE::Tunnel::fork_ssh_tunnel($self->{rem_ssh}, $cmd, $ssh_forward_info, $log);\n}\n\nsub fork_websocket_tunnel {\n    my ($self, $storages, $bridges) = @_;\n\n    my $remote = $self->{opts}->{remote};\n    my $conn = $remote->{conn};\n\n    my $log = sub {\n        my ($level, $msg) = @_;\n        $self->log($level, $msg);\n    };\n\n    my $websocket_url =\n        \"https://$conn->{host}:$conn->{port}/api2/json/nodes/$self->{node}/qemu/$remote->{vmid}/mtunnelwebsocket\";\n    my $url = \"/nodes/$self->{node}/qemu/$remote->{vmid}/mtunnel\";\n\n    my $tunnel_params = {\n        url => $websocket_url,\n    };\n\n    my $storage_list = join(',', keys %$storages);\n    my $bridge_list = join(',', keys %$bridges);\n\n    my $req_params = {\n        storages => $storage_list,\n        bridges => $bridge_list,\n    };\n\n    return PVE::Tunnel::fork_websocket_tunnel($conn, $url, $req_params, $tunnel_params, $log);\n}\n\n# tunnel_info:\n#   proto: unix (secure) or tcp (insecure/legacy compat)\n#   addr: IP or UNIX socket path\n#   port: optional TCP port\n#   unix_sockets: additional UNIX socket paths to forward\nsub start_remote_tunnel {\n    my ($self, $tunnel_info) = @_;\n\n    my $nodename = PVE::INotify::nodename();\n    my $migration_type = $self->{opts}->{migration_type};\n\n    if ($migration_type eq 'secure') {\n\n        if ($tunnel_info->{proto} eq 'unix') {\n            my $ssh_forward_info = [];\n\n            my $unix_sockets = [keys %{ $tunnel_info->{unix_sockets} }];\n            push @$unix_sockets, $tunnel_info->{addr};\n            for my $sock (@$unix_sockets) {\n                push @$ssh_forward_info, \"$sock:$sock\";\n                unlink $sock;\n            }\n\n            $self->{tunnel} = $self->fork_tunnel($ssh_forward_info);\n\n            my $unix_socket_try = 0; # wait for the socket to become ready\n            while ($unix_socket_try <= 100) {\n                $unix_socket_try++;\n                my $available = 0;\n                foreach my $sock (@$unix_sockets) {\n                    if (-S $sock) {\n                        $available++;\n                    }\n                }\n\n                if ($available == @$unix_sockets) {\n                    last;\n                }\n\n                usleep(50000);\n            }\n            if ($unix_socket_try > 100) {\n                $self->{errors} = 1;\n                PVE::Tunnel::finish_tunnel($self->{tunnel});\n                die \"Timeout, migration socket $tunnel_info->{addr} did not get ready\";\n            }\n            $self->{tunnel}->{unix_sockets} = $unix_sockets if (@$unix_sockets);\n\n        } elsif ($tunnel_info->{proto} eq 'tcp') {\n            my $ssh_forward_info = [];\n            if ($tunnel_info->{addr} eq \"localhost\") {\n                # for backwards compatibility with older qemu-server versions\n                my $pfamily = PVE::Tools::get_host_address_family($nodename);\n                my $lport = PVE::Tools::next_migrate_port($pfamily);\n                push @$ssh_forward_info, \"$lport:localhost:$tunnel_info->{port}\";\n            }\n\n            $self->{tunnel} = $self->fork_tunnel($ssh_forward_info);\n\n        } else {\n            die \"unsupported protocol in migration URI: $tunnel_info->{proto}\\n\";\n        }\n    } else {\n        #fork tunnel for insecure migration, to send faster commands like resume\n        $self->{tunnel} = $self->fork_tunnel();\n    }\n}\n\nsub lock_vm {\n    my ($self, $vmid, $code, @param) = @_;\n\n    return PVE::QemuConfig->lock_config($vmid, $code, @param);\n}\n\nsub target_storage_check_available {\n    my ($self, $storecfg, $targetsid, $volid) = @_;\n\n    if (!$self->{opts}->{remote}) {\n        # check if storage is available on target node\n        my $target_scfg = PVE::Storage::storage_check_enabled(\n            $storecfg, $targetsid, $self->{node},\n        );\n        my ($vtype) = PVE::Storage::parse_volname($storecfg, $volid);\n        die \"$volid: content type '$vtype' is not available on storage '$targetsid'\\n\"\n            if !$target_scfg->{content}->{$vtype};\n    }\n}\n\nsub prepare {\n    my ($self, $vmid) = @_;\n\n    my $online = $self->{opts}->{online};\n\n    my $storecfg = $self->{storecfg} = PVE::Storage::config();\n\n    # updates the configuration, so ordered before saving the configuration in $self\n    eval {\n        PVE::QemuConfig::cleanup_fleecing_images(\n            $vmid,\n            $storecfg,\n            sub { $self->log($_[0], $_[1]); },\n        );\n    };\n    $self->log('warn', \"attempt to clean up left-over fleecing images failed - $@\") if $@;\n\n    # test if VM exists\n    my $conf = $self->{vmconf} = PVE::QemuConfig->load_config($vmid);\n\n    my $repl_conf = PVE::ReplicationConfig->new();\n    $self->{replication_jobcfg} = $repl_conf->find_local_replication_job($vmid, $self->{node});\n    $self->{is_replicated} = $repl_conf->check_for_existing_jobs($vmid, 1);\n\n    if ($self->{replication_jobcfg} && defined($self->{replication_jobcfg}->{remove_job})) {\n        die \"refusing to migrate replicated VM whose replication job is marked for removal\\n\";\n    }\n\n    PVE::QemuConfig->check_lock($conf);\n\n    my $running = 0;\n    if (my $pid = PVE::QemuServer::check_running($vmid)) {\n        die \"can't migrate running VM without --online\\n\" if !$online;\n        $running = $pid;\n\n        if ($self->{is_replicated} && !$self->{replication_jobcfg}) {\n            if ($self->{opts}->{force}) {\n                $self->log(\n                    'warn',\n                    \"WARNING: Node '$self->{node}' is not a replication target. Existing \"\n                        . \"replication jobs will fail after migration!\\n\",\n                );\n            } else {\n                die \"Cannot live-migrate replicated VM to node '$self->{node}' - not a replication \"\n                    . \"target. Use 'force' to override.\\n\";\n            }\n        }\n\n        $self->{forcemachine} = PVE::QemuServer::Machine::qemu_machine_pxe($vmid, $conf);\n\n        # To support abstracted CPU configurations, keep QEMU's \"-cpu\" parameter intact.\n        if ($conf->{cpu} && PVE::QemuServer::CPUConfig::is_abstracted($conf->{cpu})) {\n            $self->{forcecpu} = PVE::QemuServer::CPUConfig::get_cpu_from_running_vm($pid);\n        }\n\n        # Do not treat a suspended VM as paused, as it might wake up\n        # during migration and remain paused after migration finishes.\n        $self->{vm_was_paused} = 1 if PVE::QemuServer::vm_is_paused($vmid, 0);\n\n        if ($self->{opts}->{'with-conntrack-state'}) {\n            if ($self->{opts}->{remote}) {\n                # shouldn't be reached in normal circumstances anyway, as we prevent it on\n                # an API level\n                $self->log(\n                    'warn',\n                    'conntrack state migration not supported for remote migrations, '\n                        . 'active connections might get dropped',\n                );\n                $self->{opts}->{'with-conntrack-state'} = 0;\n            } else {\n                PVE::QemuServer::DBusVMState::qemu_add_dbus_vmstate($vmid);\n            }\n        } else {\n            $self->log(\n                'warn',\n                'conntrack state migration not supported or disabled, '\n                    . 'active connections might get dropped',\n            );\n\n            # In case some leftover instance is running, stop it. The target QEMU instance won't\n            # have the 'dbus-vmstate' object, so the source must not have it either.\n            if (defined(PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($vmid, quiet => 1))) {\n                $self->log('warn', \"stopped left-over dbus-vmstate helper for VM $vmid\");\n            }\n        }\n    }\n\n    my ($loc_res, $mapped_res, $missing_mappings_by_node) =\n        PVE::QemuMigrate::Helpers::check_local_resources($conf, $running, 1);\n    my $blocking_resources = [];\n    for my $res ($loc_res->@*) {\n        if (!defined($mapped_res->{$res})) {\n            push $blocking_resources->@*, $res;\n        }\n    }\n    if (scalar($blocking_resources->@*)) {\n        if ($self->{running} || !$self->{opts}->{force}) {\n            die \"can't migrate VM which uses local devices: \"\n                . join(\", \", $blocking_resources->@*) . \"\\n\";\n        } else {\n            $self->log('info', \"migrating VM which uses local devices\");\n        }\n    }\n\n    if (scalar(keys $mapped_res->%*)) {\n        my $missing_mappings = $missing_mappings_by_node->{ $self->{node} };\n        my $missing_live_mappings = [];\n        for my $key (sort keys $mapped_res->%*) {\n            my $res = $mapped_res->{$key};\n            my $name = \"$key:$res->{name}\";\n            push $missing_live_mappings->@*, $name if !$res->{'live-migration'};\n        }\n        if (scalar($missing_mappings->@*)) {\n            my $missing = join(\", \", $missing_mappings->@*);\n            die \"can't migrate to '$self->{node}': missing mapped devices $missing\\n\";\n        } elsif ($running && scalar($missing_live_mappings->@*)) {\n            my $missing = join(\", \", $missing_live_mappings->@*);\n            die \"can't live migrate running VM which uses following mapped devices: $missing\\n\";\n        } else {\n            $self->log('info', \"migrating VM which uses mapped local devices\");\n        }\n    }\n\n    my $vga = PVE::QemuServer::parse_vga($conf->{vga});\n    if ($running && $vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {\n        my $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n        if (!PVE::QemuServer::Machine::is_machine_version_at_least($machine_version, 10, 1)) {\n            die \"VMs with 'clipboard' set to 'vnc' are not live migratable with\"\n                . \" QEMU/machine versions older than 10.1!\\n\";\n        }\n    }\n\n    my $vollist = PVE::QemuServer::get_vm_volumes($conf);\n\n    my $storages = {};\n    foreach my $volid (@$vollist) {\n        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n\n        # check if storage is available on source node\n        my $scfg = PVE::Storage::storage_check_enabled($storecfg, $sid);\n\n        my $targetsid = $sid;\n        # NOTE: local ignores shared mappings, remote maps them\n        if (!$scfg->{shared} || $self->{opts}->{remote}) {\n            $targetsid = PVE::JSONSchema::map_id($self->{opts}->{storagemap}, $sid);\n        }\n\n        $storages->{$targetsid} = 1;\n\n        $self->target_storage_check_available($storecfg, $targetsid, $volid);\n\n        if ($scfg->{shared}) {\n            # PVE::Storage::activate_storage checks this for non-shared storages\n            my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});\n            warn \"Used shared storage '$sid' is not online on source node!\\n\"\n                if !$plugin->check_connection($sid, $scfg);\n        }\n    }\n\n    if ($self->{opts}->{remote}) {\n        # test & establish websocket connection\n        my $bridges = map_bridges($conf, $self->{opts}->{bridgemap}, 1);\n        my $tunnel = $self->fork_websocket_tunnel($storages, $bridges);\n        my $min_version = $tunnel->{version} - $tunnel->{age};\n        $self->log('info', \"local WS tunnel version: $WS_TUNNEL_VERSION\");\n        $self->log('info', \"remote WS tunnel version: $tunnel->{version}\");\n        $self->log('info', \"minimum required WS tunnel version: $min_version\");\n        die \"Remote tunnel endpoint not compatible, upgrade required\\n\"\n            if $WS_TUNNEL_VERSION < $min_version;\n        die \"Remote tunnel endpoint too old, upgrade required\\n\"\n            if $WS_TUNNEL_VERSION > $tunnel->{version};\n\n        print \"websocket tunnel started\\n\";\n        $self->{tunnel} = $tunnel;\n    } else {\n        # test ssh connection\n        my $cmd = [@{ $self->{rem_ssh} }, '/bin/true'];\n        eval { $self->cmd_quiet($cmd); };\n        die \"Can't connect to destination address using public key\\n\" if $@;\n    }\n\n    return $running;\n}\n\nsub scan_local_volumes {\n    my ($self, $vmid) = @_;\n\n    my $conf = $self->{vmconf};\n\n    # local volumes which have been copied\n    # and their old_id => new_id pairs\n    $self->{volume_map} = {};\n    $self->{local_volumes} = {};\n\n    my $storecfg = $self->{storecfg};\n    eval {\n        # found local volumes and their origin\n        my $local_volumes = $self->{local_volumes};\n        my $local_volumes_errors = {};\n        my $other_errors = [];\n        my $abort = 0;\n        my $path_to_volid = {};\n\n        my $log_error = sub {\n            my ($msg, $volid) = @_;\n\n            if (defined($volid)) {\n                $local_volumes_errors->{$volid} = $msg;\n            } else {\n                push @$other_errors, $msg;\n            }\n            $abort = 1;\n        };\n\n        my $replicatable_volumes =\n            !$self->{replication_jobcfg}\n            ? {}\n            : PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 1);\n        foreach my $volid (keys %{$replicatable_volumes}) {\n            $local_volumes->{$volid}->{replicated} = 1;\n        }\n\n        my $test_volid = sub {\n            my ($volid, $attr) = @_;\n\n            if ($volid =~ m|^/|) {\n                return if $attr->{shared};\n                $local_volumes->{$volid}->{ref} = 'config';\n                die \"local file/device\\n\";\n            }\n\n            my $snaprefs = $attr->{referenced_in_snapshot};\n\n            if ($attr->{cdrom}) {\n                if ($volid eq 'cdrom') {\n                    my $msg = \"can't migrate local cdrom drive\";\n                    if (defined($snaprefs) && !$attr->{is_attached}) {\n                        my $snapnames = join(', ', sort keys %$snaprefs);\n                        $msg .= \" (referenced in snapshot - $snapnames)\";\n                    }\n                    &$log_error(\"$msg\\n\");\n                    return;\n                }\n                return if $volid eq 'none';\n            }\n\n            my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n            # check if storage is available on both nodes\n            my $scfg = PVE::Storage::storage_check_enabled($storecfg, $sid);\n\n            my $targetsid = $sid;\n            # NOTE: local ignores shared mappings, remote maps them\n            if (!$scfg->{shared} || $self->{opts}->{remote}) {\n                $targetsid = PVE::JSONSchema::map_id($self->{opts}->{storagemap}, $sid);\n            }\n\n            $self->target_storage_check_available($storecfg, $targetsid, $volid);\n            return if $scfg->{shared} && !$self->{opts}->{remote};\n\n            $local_volumes->{$volid}->{ref} = 'pending' if $attr->{referenced_in_pending};\n            $local_volumes->{$volid}->{ref} = 'snapshot' if $attr->{referenced_in_snapshot};\n            $local_volumes->{$volid}->{ref} = 'unused' if $attr->{is_unused};\n            $local_volumes->{$volid}->{ref} = 'attached' if $attr->{is_attached};\n            $local_volumes->{$volid}->{ref} = 'generated' if $attr->{is_tpmstate};\n\n            $local_volumes->{$volid}->{bwlimit} = $self->get_bwlimit($sid, $targetsid);\n            $local_volumes->{$volid}->{targetsid} = $targetsid;\n\n            $local_volumes->{$volid}->@{qw(size format)} =\n                PVE::Storage::volume_size_info($storecfg, $volid);\n\n            $local_volumes->{$volid}->{is_vmstate} = $attr->{is_vmstate} ? 1 : 0;\n            $local_volumes->{$volid}->{is_cloudinit} = $attr->{is_cloudinit} ? 1 : 0;\n\n            $local_volumes->{$volid}->{drivename} = $attr->{drivename}\n                if $attr->{drivename};\n\n            # If with_snapshots is not set for storage migrate, it tries to use\n            # a raw+size stream, but on-the-fly conversion from qcow2 to raw+size\n            # back to qcow2 is currently not possible.\n            $local_volumes->{$volid}->{snapshots} =\n                ($local_volumes->{$volid}->{format} =~ /^(?:qcow2|vmdk)$/);\n\n            if ($attr->{cdrom}) {\n                if ($volid =~ /vm-\\d+-cloudinit/) {\n                    $local_volumes->{$volid}->{ref} = 'generated';\n                    return;\n                }\n                die \"local cdrom image\\n\";\n            }\n\n            my ($path, $owner) = PVE::Storage::path($storecfg, $volid);\n\n            die \"owned by other VM (owner = VM $owner)\\n\"\n                if !$owner || ($owner != $vmid);\n\n            $path_to_volid->{$path}->{$volid} = 1;\n\n            return if $attr->{is_vmstate};\n\n            if (defined($snaprefs)) {\n                $local_volumes->{$volid}->{snapshots} = 1;\n\n                # we cannot migrate snapshots on local storage\n                # exceptions: 'zfspool' or 'qcow2' files (on directory storage)\n                #\n                # Note that only qcow2 with in-qcow2 snapshots work - for\n                # backing-chain snapshots we'd need to copy the entire snapshot\n                # list (or support replication)\n\n                die \"online storage migration not possible if non-replicated snapshot exists\\n\"\n                    if $self->{running} && !$local_volumes->{$volid}->{replicated};\n\n                die \"remote migration with snapshots not supported yet\\n\"\n                    if $self->{opts}->{remote};\n\n                if (!(\n                    $scfg->{type} eq 'zfspool'\n                    || ($scfg->{type} eq 'btrfs' && $local_volumes->{$volid}->{format} eq 'raw')\n                    || ($local_volumes->{$volid}->{format} eq 'qcow2'\n                        && !$scfg->{'snapshot-as-volume-chain'})\n                )) {\n                    die \"non-migratable snapshot exists\\n\";\n                }\n            }\n\n            die \"referenced by linked clone(s)\\n\"\n                if PVE::Storage::volume_is_base_and_used($storecfg, $volid);\n        };\n\n        PVE::QemuServer::foreach_volid(\n            $conf,\n            sub {\n                my ($volid, $attr) = @_;\n                eval { $test_volid->($volid, $attr); };\n                if (my $err = $@) {\n                    &$log_error($err, $volid);\n                }\n            },\n        );\n\n        for my $path (keys %$path_to_volid) {\n            my @volids = keys $path_to_volid->{$path}->%*;\n            die \"detected not supported aliased volumes: '\" . join(\"', '\", @volids) . \"'\\n\"\n                if (scalar(@volids) > 1);\n        }\n\n        foreach my $vol (sort keys %$local_volumes) {\n            my $type = $replicatable_volumes->{$vol} ? 'local, replicated' : 'local';\n            my $ref = $local_volumes->{$vol}->{ref};\n            if ($ref eq 'attached') {\n                &$log_error(\n                    \"can't live migrate attached local disks without with-local-disks option\\n\",\n                    $vol,\n                ) if $self->{running} && !$self->{opts}->{\"with-local-disks\"};\n                $self->log('info', \"found $type disk '$vol' (attached)\\n\");\n            } elsif ($ref eq 'unused') {\n                $self->log('info', \"found $type disk '$vol' (unused)\\n\");\n            } elsif ($ref eq 'snapshot') {\n                $self->log('info', \"found $type disk '$vol' (referenced by snapshot(s))\\n\");\n            } elsif ($ref eq 'pending') {\n                $self->log('info', \"found $type disk '$vol' (pending change)\\n\");\n            } elsif ($ref eq 'generated') {\n                $self->log('info', \"found generated disk '$vol' (in current VM config)\\n\");\n            } else {\n                $self->log('info', \"found $type disk '$vol'\\n\");\n            }\n        }\n\n        foreach my $vol (sort keys %$local_volumes_errors) {\n            $self->log('warn',\n                \"can't migrate local disk '$vol': $local_volumes_errors->{$vol}\");\n        }\n        foreach my $err (@$other_errors) {\n            $self->log('warn', \"$err\");\n        }\n\n        if ($abort) {\n            die \"can't migrate VM - check log\\n\";\n        }\n\n        # additional checks for local storage\n        foreach my $volid (keys %$local_volumes) {\n            my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);\n            my $scfg = PVE::Storage::storage_config($storecfg, $sid);\n\n            my $migratable = $scfg->{type} =~ /^(?:dir|btrfs|zfspool|lvmthin|lvm)$/;\n\n            # TODO: what is this even here for?\n            $migratable = 1 if $self->{opts}->{remote};\n\n            die \"can't migrate '$volid' - storage type '$scfg->{type}' not supported\\n\"\n                if !$migratable;\n\n            # image is a linked clone on local storage, se we can't migrate.\n            if (my $basename = (PVE::Storage::parse_volname($storecfg, $volid))[3]) {\n                die \"can't migrate '$volid' as it's a clone of '$basename'\";\n            }\n        }\n\n        foreach my $volid (sort keys %$local_volumes) {\n            my $ref = $local_volumes->{$volid}->{ref};\n            if ($self->{running} && $ref eq 'attached') {\n                $local_volumes->{$volid}->{migration_mode} = 'online';\n            } elsif ($self->{running} && $ref eq 'generated') {\n                # offline migrate the cloud-init ISO and don't regenerate on VM start\n                #\n                # tpmstate will also be offline migrated first, and in case of\n                # live migration then updated by QEMU/swtpm if necessary\n                $local_volumes->{$volid}->{migration_mode} = 'offline';\n            } else {\n                $local_volumes->{$volid}->{migration_mode} = 'offline';\n            }\n        }\n    };\n    die \"Problem found while scanning volumes - $@\" if $@;\n}\n\nsub handle_replication {\n    my ($self, $vmid) = @_;\n\n    my $conf = $self->{vmconf};\n    my $local_volumes = $self->{local_volumes};\n\n    return if !$self->{replication_jobcfg};\n\n    die \"can't migrate VM with replicated volumes to remote cluster/node\\n\"\n        if $self->{opts}->{remote};\n\n    if ($self->{running}) {\n        my @live_replicatable_volumes = $self->filter_local_volumes('online', 1);\n        foreach my $volid (@live_replicatable_volumes) {\n            my $drive = $local_volumes->{$volid}->{drivename};\n            die \"internal error - no drive for '$volid'\\n\" if !defined($drive);\n\n            my $bitmap = \"repl_$drive\";\n\n            # start tracking before replication to get full delta + a few duplicates\n            $self->log('info', \"$drive: start tracking writes using block-dirty-bitmap '$bitmap'\");\n            mon_cmd($vmid, 'block-dirty-bitmap-add', node => \"drive-$drive\", name => $bitmap);\n\n            # other info comes from target node in phase 2\n            $self->{target_drive}->{$drive}->{bitmap} = $bitmap;\n        }\n    }\n    $self->log('info', \"replicating disk images\");\n\n    my $start_time = time();\n    my $logfunc = sub { $self->log('info', shift) };\n    my $actual_replicated_volumes = PVE::Replication::run_replication(\n        'PVE::QemuConfig',\n        $self->{replication_jobcfg},\n        $start_time,\n        $start_time,\n        $logfunc,\n    );\n\n    # extra safety check\n    my @replicated_volumes = $self->filter_local_volumes(undef, 1);\n    foreach my $volid (@replicated_volumes) {\n        die \"expected volume '$volid' to get replicated, but it wasn't\\n\"\n            if !$actual_replicated_volumes->{$volid};\n    }\n}\n\nsub config_update_local_disksizes {\n    my ($self) = @_;\n\n    my $conf = $self->{vmconf};\n    my $local_volumes = $self->{local_volumes};\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($key, $drive) = @_;\n            # skip special disks, will be handled later\n            return if $key eq 'efidisk0';\n            return if $key eq 'tpmstate0';\n\n            my $volid = $drive->{file};\n            return if !defined($local_volumes->{$volid}); # only update sizes for local volumes\n\n            my ($updated, $msg) =\n                PVE::QemuServer::Drive::update_disksize($drive, $local_volumes->{$volid}->{size});\n            if (defined($updated)) {\n                $conf->{$key} = PVE::QemuServer::print_drive($updated);\n                $self->log('info', \"drive '$key': $msg\");\n            }\n        },\n    );\n\n    # we want to set the efidisk size in the config to the size of the\n    # real OVMF_VARS.fd image, else we can create a too big image, which does not work\n    if (defined($conf->{efidisk0})) {\n        PVE::QemuServer::update_efidisk_size($conf);\n    }\n\n    # TPM state might have an irregular filesize, to avoid problems on transfer\n    # we always assume the static size of 4M to allocate on the target\n    if (defined($conf->{tpmstate0})) {\n        PVE::QemuServer::update_tpmstate_size($conf);\n    }\n}\n\nsub filter_local_volumes {\n    my ($self, $migration_mode, $replicated) = @_;\n\n    my $volumes = $self->{local_volumes};\n    my @filtered_volids;\n\n    foreach my $volid (sort keys %{$volumes}) {\n        next\n            if defined($migration_mode)\n            && safe_string_ne($volumes->{$volid}->{migration_mode}, $migration_mode);\n        next\n            if defined($replicated)\n            && safe_boolean_ne($volumes->{$volid}->{replicated}, $replicated);\n        push @filtered_volids, $volid;\n    }\n\n    return @filtered_volids;\n}\n\nsub sync_offline_local_volumes {\n    my ($self) = @_;\n\n    my $local_volumes = $self->{local_volumes};\n    my @volids = $self->filter_local_volumes('offline', 0);\n\n    my $storecfg = $self->{storecfg};\n    my $opts = $self->{opts};\n\n    $self->log('info', \"copying local disk images\") if scalar(@volids);\n\n    foreach my $volid (@volids) {\n        my $new_volid;\n\n        my $opts = $self->{opts};\n        if ($opts->{remote}) {\n            my $log = sub {\n                my ($level, $msg) = @_;\n                $self->log($level, $msg);\n            };\n\n            $new_volid = PVE::StorageTunnel::storage_migrate(\n                $self->{tunnel},\n                $storecfg,\n                $volid,\n                $self->{vmid},\n                $opts->{remote}->{vmid},\n                $local_volumes->{$volid},\n                $log,\n            );\n        } else {\n            my $targetsid = $local_volumes->{$volid}->{targetsid};\n\n            my $bwlimit = $local_volumes->{$volid}->{bwlimit};\n            $bwlimit = $bwlimit * 1024 if defined($bwlimit); # storage_migrate uses bps\n\n            my $preserve_name =\n                $local_volumes->{$volid}->{is_vmstate} || $local_volumes->{$volid}->{is_cloudinit};\n\n            my $storage_migrate_opts = {\n                'ratelimit_bps' => $bwlimit,\n                'insecure' => $opts->{migration_type} eq 'insecure',\n                'with_snapshots' => $local_volumes->{$volid}->{snapshots},\n                'allow_rename' => !$preserve_name,\n            };\n\n            my $logfunc = sub { $self->log('info', $_[0]); };\n            $new_volid = eval {\n                PVE::Storage::storage_migrate(\n                    $storecfg,\n                    $volid,\n                    $self->{ssh_info},\n                    $targetsid,\n                    $storage_migrate_opts,\n                    $logfunc,\n                );\n            };\n            if (my $err = $@) {\n                die \"storage migration for '$volid' to storage '$targetsid' failed - $err\\n\";\n            }\n        }\n\n        $self->{volume_map}->{$volid} = $new_volid;\n        $self->log('info', \"volume '$volid' is '$new_volid' on the target\\n\");\n\n        eval { PVE::Storage::deactivate_volumes($storecfg, [$volid]); };\n        if (my $err = $@) {\n            $self->log('warn', $err);\n        }\n    }\n}\n\nsub cleanup_remotedisks {\n    my ($self) = @_;\n\n    if ($self->{opts}->{remote}) {\n        PVE::Tunnel::finish_tunnel($self->{tunnel}, 1);\n        delete $self->{tunnel};\n        return;\n    }\n\n    my $local_volumes = $self->{local_volumes};\n\n    foreach my $volid (values %{ $self->{volume_map} }) {\n        # don't clean up replicated disks!\n        next if $local_volumes->{$volid}->{replicated};\n\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n        my $cmd = [@{ $self->{rem_ssh} }, 'pvesm', 'free', \"$storeid:$volname\"];\n\n        eval {\n            PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => sub { });\n        };\n        if (my $err = $@) {\n            $self->log('err', $err);\n            $self->{errors} = 1;\n        }\n    }\n}\n\nsub cleanup_bitmaps {\n    my ($self) = @_;\n    foreach my $drive (keys %{ $self->{target_drive} }) {\n        my $bitmap = $self->{target_drive}->{$drive}->{bitmap};\n        next if !$bitmap;\n        $self->log('info', \"$drive: removing block-dirty-bitmap '$bitmap'\");\n        mon_cmd(\n            $self->{vmid}, 'block-dirty-bitmap-remove',\n            node => \"drive-$drive\",\n            name => $bitmap,\n        );\n    }\n}\n\nsub phase1 {\n    my ($self, $vmid) = @_;\n\n    $self->log('info', \"starting migration of VM $vmid to node '$self->{node}' ($self->{nodeip})\");\n\n    my $conf = $self->{vmconf};\n\n    # set migrate lock in config file\n    $conf->{lock} = 'migrate';\n    PVE::QemuConfig->write_config($vmid, $conf);\n\n    $self->scan_local_volumes($vmid);\n\n    # fix disk sizes to match their actual size and write changes,\n    # so that the target allocates the correct volumes\n    $self->config_update_local_disksizes();\n    PVE::QemuConfig->write_config($vmid, $conf);\n\n    $self->handle_replication($vmid);\n\n    $self->sync_offline_local_volumes();\n    $self->phase1_remote($vmid) if $self->{opts}->{remote};\n}\n\nsub map_bridges {\n    my ($conf, $map, $scan_only) = @_;\n\n    my $bridges = {};\n\n    foreach my $opt (keys %$conf) {\n        next if $opt !~ m/^net\\d+$/;\n\n        next if !$conf->{$opt};\n        my $d = PVE::QemuServer::Network::parse_net($conf->{$opt});\n        next if !$d || !$d->{bridge};\n\n        my $target_bridge = PVE::JSONSchema::map_id($map, $d->{bridge});\n        $bridges->{$target_bridge}->{$opt} = $d->{bridge};\n\n        next if $scan_only;\n\n        $d->{bridge} = $target_bridge;\n        $conf->{$opt} = PVE::QemuServer::Network::print_net($d);\n    }\n\n    return $bridges;\n}\n\nsub phase1_remote {\n    my ($self, $vmid) = @_;\n\n    my $remote_conf = PVE::QemuConfig->load_config($vmid);\n    PVE::QemuConfig->update_volume_ids($remote_conf, $self->{volume_map});\n\n    my $bridges = map_bridges($remote_conf, $self->{opts}->{bridgemap});\n    for my $target (keys $bridges->%*) {\n        for my $nic (keys $bridges->{$target}->%*) {\n            $self->log('info', \"mapped: $nic from $bridges->{$target}->{$nic} to $target\");\n        }\n    }\n\n    my @online_local_volumes = $self->filter_local_volumes('online');\n\n    my $storage_map = $self->{opts}->{storagemap};\n    $self->{nbd} = {};\n    PVE::QemuConfig->foreach_volume(\n        $remote_conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            # TODO eject CDROM?\n            return if PVE::QemuServer::drive_is_cdrom($drive);\n\n            my $volid = $drive->{file};\n            return if !$volid;\n\n            return if !grep { $_ eq $volid } @online_local_volumes;\n\n            my ($storeid) = PVE::Storage::parse_volume_id($volid);\n            my $source_format = checked_volume_format($self->{storecfg}, $volid);\n\n            # set by target cluster\n            my $oldvolid = delete $drive->{file};\n            delete $drive->{format};\n\n            my $targetsid = PVE::JSONSchema::map_id($storage_map, $storeid);\n\n            my $params = {\n                format => $source_format,\n                storage => $targetsid,\n                drive => $drive,\n            };\n\n            $self->log(\n                'info',\n                \"Allocating volume for drive '$ds' on remote storage '$targetsid'..\",\n            );\n            my $res = PVE::Tunnel::write_tunnel($self->{tunnel}, 600, 'disk', $params);\n\n            $self->log('info', \"volume '$oldvolid' is '$res->{volid}' on the target\\n\");\n            $remote_conf->{$ds} = $res->{drivestr};\n            $self->{nbd}->{$ds} = $res;\n        },\n    );\n\n    my $conf_str = PVE::QemuServer::write_vm_config(\"remote\", $remote_conf);\n\n    # TODO expose in PVE::Firewall?\n    my $vm_fw_conf_path = \"/etc/pve/firewall/$vmid.fw\";\n    my $fw_conf_str;\n    $fw_conf_str = PVE::Tools::file_get_contents($vm_fw_conf_path)\n        if -e $vm_fw_conf_path;\n    my $params = {\n        conf => $conf_str,\n        'firewall-config' => $fw_conf_str,\n    };\n\n    PVE::Tunnel::write_tunnel($self->{tunnel}, 120, 'config', $params);\n}\n\nsub phase1_cleanup {\n    my ($self, $vmid, $err) = @_;\n\n    $self->log('info', \"aborting phase 1 - cleanup resources\");\n\n    my $conf = $self->{vmconf};\n    delete $conf->{lock};\n    eval { PVE::QemuConfig->write_config($vmid, $conf) };\n    if (my $err = $@) {\n        $self->log('err', $err);\n    }\n\n    eval { $self->cleanup_remotedisks() };\n    if (my $err = $@) {\n        $self->log('err', $err);\n    }\n\n    eval { $self->cleanup_bitmaps() };\n    if (my $err = $@) {\n        $self->log('err', $err);\n    }\n\n    if ($self->{running} && $self->{opts}->{'with-conntrack-state'}) {\n        # if the VM is running, that means we also tried to migrate additional\n        # state via our dbus-vmstate helper\n        # only need to locally stop it, on the target the VM cleanup will\n        # handle it\n        PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($vmid);\n    }\n}\n\nsub phase2_start_local_cluster {\n    my ($self, $vmid, $params) = @_;\n\n    my $conf = $self->{vmconf};\n    my $local_volumes = $self->{local_volumes};\n    my @online_local_volumes = $self->filter_local_volumes('online');\n\n    my $start = $params->{start_params};\n    my $migrate = $params->{migrate_opts};\n\n    $self->log('info', \"starting VM $vmid on remote node '$self->{node}'\");\n\n    my $tunnel_info = {};\n\n    ## start on remote node\n    my $cmd = [@{ $self->{rem_ssh} }];\n\n    push @$cmd, 'qm', 'start', $vmid;\n\n    if ($start->{skiplock}) {\n        push @$cmd, '--skiplock';\n    }\n\n    push @$cmd, '--migratedfrom', $migrate->{migratedfrom};\n\n    push @$cmd, '--migration_type', $migrate->{type};\n\n    push @$cmd, '--migration_network', $migrate->{network}\n        if $migrate->{network};\n\n    push @$cmd, '--stateuri', $start->{statefile};\n\n    if ($start->{forcemachine}) {\n        push @$cmd, '--machine', $start->{forcemachine};\n    }\n\n    if ($start->{forcecpu}) {\n        push @$cmd, '--force-cpu', $start->{forcecpu};\n    }\n\n    if ($start->{'nets-host-mtu'}) {\n        push @$cmd, '--nets-host-mtu', $start->{'nets-host-mtu'};\n    }\n\n    if ($self->{storage_migration}) {\n        push @$cmd, '--targetstorage', ($self->{opts}->{targetstorage} // '1');\n    }\n\n    if ($self->{opts}->{'with-conntrack-state'}) {\n        push @$cmd, '--with-conntrack-state';\n    }\n\n    my $spice_port;\n    my $input = \"nbd_protocol_version: $migrate->{nbd_proto_version}\\n\";\n\n    my @offline_local_volumes = $self->filter_local_volumes('offline');\n    for my $volid (@offline_local_volumes) {\n        my $drivename = $local_volumes->{$volid}->{drivename};\n        next if !$drivename || !$conf->{$drivename};\n\n        my $new_volid = $self->{volume_map}->{$volid};\n        next if !$new_volid || $volid eq $new_volid;\n\n        $input .= \"offline_volume: $drivename: $new_volid\\n\";\n    }\n\n    $input .= \"spice_ticket: $migrate->{spice_ticket}\\n\" if $migrate->{spice_ticket};\n\n    my @online_replicated_volumes = $self->filter_local_volumes('online', 1);\n    foreach my $volid (@online_replicated_volumes) {\n        $input .= \"replicated_volume: $volid\\n\";\n    }\n\n    my $handle_storage_migration_listens = sub {\n        my ($drive_key, $drivestr, $nbd_uri) = @_;\n\n        $self->{stopnbd} = 1;\n        $self->{target_drive}->{$drive_key}->{drivestr} = $drivestr;\n        $self->{target_drive}->{$drive_key}->{nbd_uri} = $nbd_uri;\n\n        my $source_drive = PVE::QemuServer::parse_drive($drive_key, $conf->{$drive_key});\n        my $target_drive = PVE::QemuServer::parse_drive($drive_key, $drivestr);\n        my $source_volid = $source_drive->{file};\n        my $target_volid = $target_drive->{file};\n\n        $self->{volume_map}->{$source_volid} = $target_volid;\n        $self->log('info', \"volume '$source_volid' is '$target_volid' on the target\\n\");\n    };\n\n    my $target_replicated_volumes = {};\n    my $target_nets_host_mtu_not_supported;\n\n    # Note: We try to keep $spice_ticket secret (do not pass via command line parameter)\n    # instead we pipe it through STDIN\n    my $exitcode = PVE::Tools::run_command(\n        $cmd,\n        input => $input,\n        outfunc => sub {\n            my $line = shift;\n\n            if ($line =~\n                m/^migration listens on (tcp):(localhost|[\\d\\.]+|\\[[\\d\\.:a-fA-F]+\\]):(\\d+)$/\n            ) {\n                $tunnel_info->{addr} = $2;\n                $tunnel_info->{port} = int($3);\n                $tunnel_info->{proto} = $1;\n            } elsif ($line =~ m!^migration listens on (unix):(/run/qemu-server/(\\d+)\\.migrate)$!\n            ) {\n                $tunnel_info->{addr} = $2;\n                die \"Destination UNIX sockets VMID does not match source VMID\" if $vmid ne $3;\n                $tunnel_info->{proto} = $1;\n            } elsif ($line =~ m/^migration listens on port (\\d+)$/) {\n                $tunnel_info->{addr} = \"localhost\";\n                $tunnel_info->{port} = int($1);\n                $tunnel_info->{proto} = \"tcp\";\n            } elsif ($line =~ m/^spice listens on port (\\d+)$/) {\n                $spice_port = int($1);\n            } elsif ($line =~\n                m/^storage migration listens on nbd:(localhost|[\\d\\.]+|\\[[\\d\\.:a-fA-F]+\\]):(\\d+):exportname=(\\S+) volume:(\\S+)$/\n            ) {\n                my $drivestr = $4;\n                my $nbd_uri = \"nbd:$1:$2:exportname=$3\";\n                my $targetdrive = $3;\n                $targetdrive =~ s/drive-//g;\n\n                $handle_storage_migration_listens->($targetdrive, $drivestr, $nbd_uri);\n            } elsif ($line =~\n                m!^storage migration listens on nbd:unix:(/run/qemu-server/(\\d+)_nbd\\.migrate):exportname=(\\S+) volume:(\\S+)$!\n            ) {\n                my $drivestr = $4;\n                die \"Destination UNIX socket's VMID does not match source VMID\" if $vmid ne $2;\n                my $nbd_unix_addr = $1;\n                my $nbd_uri = \"nbd:unix:$nbd_unix_addr:exportname=$3\";\n                my $targetdrive = $3;\n                $targetdrive =~ s/drive-//g;\n\n                $handle_storage_migration_listens->($targetdrive, $drivestr, $nbd_uri);\n                $tunnel_info->{unix_sockets}->{$nbd_unix_addr} = 1;\n            } elsif ($line =~ m/^re-using replicated volume: (\\S+) - (.*)$/) {\n                my $drive = $1;\n                my $volid = $2;\n                $target_replicated_volumes->{$volid} = $drive;\n            } elsif ($line =~ m/^QEMU: (.*)$/) {\n                $self->log('info', \"[$self->{node}] $1\\n\");\n            }\n        },\n        errfunc => sub {\n            my $line = shift;\n            $target_nets_host_mtu_not_supported = 1\n                if $line =~ m/^Unknown option: nets-host-mtu/;\n            $self->log('info', \"[$self->{node}] $line\");\n        },\n        noerr => 1,\n    );\n\n    die \"target node $self->{node} is too old for preserving VirtIO-net MTU, please upgrade\\n\"\n        if $target_nets_host_mtu_not_supported;\n\n    die \"remote command failed with exit code $exitcode\\n\" if $exitcode;\n\n    die \"unable to detect remote migration address\\n\"\n        if !$tunnel_info->{addr} || !$tunnel_info->{proto};\n\n    if (scalar(keys %$target_replicated_volumes) != scalar(@online_replicated_volumes)) {\n        die\n            \"number of replicated disks on source and target node do not match - target node too old?\\n\";\n    }\n\n    return ($tunnel_info, $spice_port);\n}\n\nsub phase2_start_remote_cluster {\n    my ($self, $vmid, $params) = @_;\n\n    die \"insecure migration to remote cluster not implemented\\n\"\n        if $params->{migrate_opts}->{type} ne 'websocket';\n\n    my $remote_vmid = $self->{opts}->{remote}->{vmid};\n\n    # like regular start but with some overhead accounted for\n    my $memory = get_current_memory($self->{vmconf}->{memory});\n    my $timeout = PVE::QemuServer::Helpers::config_aware_timeout($self->{vmconf}, $memory) + 10;\n\n    my $res = PVE::Tunnel::write_tunnel($self->{tunnel}, $timeout, \"start\", $params);\n\n    foreach my $drive (keys %{ $res->{drives} }) {\n        $self->{stopnbd} = 1;\n        $self->{target_drive}->{$drive}->{drivestr} = $res->{drives}->{$drive}->{drivestr};\n        my $nbd_uri = $res->{drives}->{$drive}->{nbd_uri};\n        die \"unexpected NBD uri for '$drive': $nbd_uri\\n\"\n            if $nbd_uri !~ s!/run/qemu-server/$remote_vmid\\_!/run/qemu-server/$vmid\\_!;\n\n        $self->{target_drive}->{$drive}->{nbd_uri} = $nbd_uri;\n    }\n\n    return ($res->{migrate}, $res->{spice_port});\n}\n\nmy $migrate_downtime_max = 2000 * 1000; # as defined in QEMU's migration/options.c\n\nmy sub cap_migrate_downtime {\n    my ($self, $migrate_downtime) = @_;\n\n    if ($migrate_downtime > $migrate_downtime_max) {\n        $self->log('info', \"capping downtime limit to maximum possible: $migrate_downtime_max ms\");\n        return $migrate_downtime_max;\n    }\n\n    return $migrate_downtime;\n}\n\nmy sub increase_migrate_downtime {\n    my ($self, $vmid, $migrate_downtime) = @_;\n\n    return $migrate_downtime_max if $migrate_downtime >= $migrate_downtime_max;\n\n    $migrate_downtime *= 2;\n\n    $migrate_downtime = cap_migrate_downtime($self, $migrate_downtime);\n\n    $self->log(\n        'info', \"auto-increased downtime to continue migration: $migrate_downtime ms\",\n    );\n    eval {\n        # migrate-set-parameters does not touch values not\n        # specified, so this only changes downtime-limit\n        mon_cmd(\n            $vmid, \"migrate-set-parameters\", 'downtime-limit' => int($migrate_downtime),\n        );\n    };\n    $self->log('info', \"migrate-set-parameters error: $@\") if $@;\n\n    return $migrate_downtime;\n}\n\nsub phase2 {\n    my ($self, $vmid) = @_;\n\n    my $conf = $self->{vmconf};\n    my $local_volumes = $self->{local_volumes};\n\n    # version > 0 for unix socket support\n    my $nbd_protocol_version = 1;\n\n    my $spice_ticket;\n    if (PVE::QemuServer::vga_conf_has_spice($conf->{vga})) {\n        my $res = mon_cmd($vmid, 'query-spice');\n        $spice_ticket = $res->{ticket};\n    }\n\n    my $migration_type = $self->{opts}->{migration_type};\n    my $state_uri = $migration_type eq 'insecure' ? 'tcp' : 'unix';\n\n    my $params = {\n        start_params => {\n            statefile => $state_uri,\n            forcemachine => $self->{forcemachine},\n            forcecpu => $self->{forcecpu},\n            skiplock => 1,\n        },\n        migrate_opts => {\n            spice_ticket => $spice_ticket,\n            type => $migration_type,\n            network => $self->{opts}->{migration_network},\n            storagemap => $self->{opts}->{storagemap},\n            migratedfrom => PVE::INotify::nodename(),\n            nbd_proto_version => $nbd_protocol_version,\n            nbd => $self->{nbd},\n        },\n    };\n\n    if (my $nets_host_mtu = PVE::QemuServer::Network::get_nets_host_mtu($vmid, $conf)) {\n        $params->{start_params}->{'nets-host-mtu'} = $nets_host_mtu;\n    }\n\n    my ($tunnel_info, $spice_port);\n\n    my @online_local_volumes = $self->filter_local_volumes('online');\n    $self->{storage_migration} = 1 if scalar(@online_local_volumes);\n\n    if (my $remote = $self->{opts}->{remote}) {\n        my $remote_vmid = $remote->{vmid};\n        $params->{migrate_opts}->{remote_node} = $self->{node};\n        ($tunnel_info, $spice_port) = $self->phase2_start_remote_cluster($vmid, $params);\n        die \"only UNIX sockets are supported for remote migration\\n\"\n            if $tunnel_info->{proto} ne 'unix';\n\n        # untaint\n        my ($remote_socket) = $tunnel_info->{addr} =~ m|^(/run/qemu-server/\\d+\\.migrate)$|\n            or die \"unexpected socket address '$tunnel_info->{addr}'\\n\";\n        my $local_socket = $remote_socket;\n        $local_socket =~ s/$remote_vmid/$vmid/g;\n        $tunnel_info->{addr} = $local_socket;\n\n        $self->log('info', \"Setting up tunnel for '$local_socket'\");\n        PVE::Tunnel::forward_unix_socket($self->{tunnel}, $local_socket, $remote_socket);\n\n        foreach my $remote_socket (@{ $tunnel_info->{unix_sockets} }) {\n            # untaint\n            ($remote_socket) = $remote_socket =~ m|^(/run/qemu-server/(?:(?!\\.\\./).)+\\.migrate)$|\n                or die \"unexpected socket address '$remote_socket'\\n\";\n            my $local_socket = $remote_socket;\n            $local_socket =~ s/$remote_vmid/$vmid/g;\n            next if $self->{tunnel}->{forwarded}->{$local_socket};\n            $self->log('info', \"Setting up tunnel for '$local_socket'\");\n            PVE::Tunnel::forward_unix_socket($self->{tunnel}, $local_socket, $remote_socket);\n        }\n    } else {\n        ($tunnel_info, $spice_port) = $self->phase2_start_local_cluster($vmid, $params);\n\n        $self->log('info', \"start remote tunnel\");\n        $self->start_remote_tunnel($tunnel_info);\n    }\n\n    my $migrate_uri = \"$tunnel_info->{proto}:$tunnel_info->{addr}\";\n    $migrate_uri .= \":$tunnel_info->{port}\"\n        if defined($tunnel_info->{port});\n\n    if ($self->{storage_migration}) {\n        $self->{storage_migration_jobs} = {};\n        $self->log('info', \"starting storage migration\");\n\n        die \"The number of local disks does not match between the source and the destination.\\n\"\n            if (scalar(keys %{ $self->{target_drive} }) != scalar(@online_local_volumes));\n        foreach my $drive (keys %{ $self->{target_drive} }) {\n            my $target = $self->{target_drive}->{$drive};\n            my $nbd_uri = $target->{nbd_uri};\n\n            my $source_drive = PVE::QemuServer::parse_drive($drive, $conf->{$drive});\n            my $source_volid = $source_drive->{file};\n\n            my $bwlimit = $self->{local_volumes}->{$source_volid}->{bwlimit};\n            my $bitmap = $target->{bitmap};\n\n            $self->log('info', \"$drive: start migration to $nbd_uri\");\n\n            my $source_info = { vmid => $vmid, drive => $source_drive };\n            $source_info->{bitmap} = $bitmap if defined($bitmap);\n            my $dest_info = { volid => $nbd_uri };\n            my $mirror_opts = {};\n            $mirror_opts->{bwlimit} = $bwlimit if defined($bwlimit);\n            PVE::QemuServer::BlockJob::mirror(\n                $source_info,\n                $dest_info,\n                $self->{storage_migration_jobs},\n                'skip',\n                $mirror_opts,\n            );\n        }\n\n        if (PVE::QemuServer::QMPHelpers::runs_at_least_qemu_version($vmid, 8, 2)) {\n            $self->log('info', \"switching mirror jobs to actively synced mode\");\n            PVE::QemuServer::BlockJob::qemu_drive_mirror_switch_to_active_mode(\n                $vmid,\n                $self->{storage_migration_jobs},\n            );\n        }\n    }\n\n    $self->log('info', \"starting online/live migration on $migrate_uri\");\n    $self->{livemigration} = 1;\n\n    # load_defaults\n    my $defaults = PVE::QemuServer::load_defaults();\n\n    $self->log('info', \"set migration capabilities\");\n    eval { PVE::QemuMigrate::Helpers::set_migration_caps($vmid) };\n    warn $@ if $@;\n\n    my $qemu_migrate_params = {};\n\n    # migrate speed can be set via bwlimit (datacenter.cfg and API) and via the\n    # migrate_speed parameter in qm.conf - take the lower of the two.\n    my $bwlimit = $self->get_bwlimit();\n\n    my $migrate_speed = $conf->{migrate_speed} // 0;\n    $migrate_speed *= 1024; # migrate_speed is in MB/s, bwlimit in KB/s\n\n    if ($bwlimit && $migrate_speed) {\n        $migrate_speed = ($bwlimit < $migrate_speed) ? $bwlimit : $migrate_speed;\n    } else {\n        $migrate_speed ||= $bwlimit;\n    }\n    $migrate_speed ||= ($defaults->{migrate_speed} || 0) * 1024;\n\n    if ($migrate_speed) {\n        $migrate_speed *= 1024; # qmp takes migrate_speed in B/s.\n        $self->log('info', \"migration speed limit: \" . render_bytes($migrate_speed, 1) . \"/s\");\n    } else {\n        # always set migrate speed as QEMU default to 128 MiBps == 1 Gbps, use 16 GiBps == 128 Gbps\n        $migrate_speed = (16 << 30);\n    }\n    $qemu_migrate_params->{'max-bandwidth'} = int($migrate_speed);\n\n    my $migrate_downtime = $defaults->{migrate_downtime};\n    $migrate_downtime = $conf->{migrate_downtime} if defined($conf->{migrate_downtime});\n    # migrate-set-parameters expects limit in ms\n    $migrate_downtime *= 1000;\n    $migrate_downtime = cap_migrate_downtime($self, $migrate_downtime);\n    $self->log('info', \"migration downtime limit: $migrate_downtime ms\");\n    $qemu_migrate_params->{'downtime-limit'} = int($migrate_downtime);\n\n    # set cachesize to 10% of the total memory\n    my $memory = get_current_memory($conf->{memory});\n    my $cachesize = int($memory * 1048576 / 10);\n    $cachesize = round_powerof2($cachesize);\n\n    $self->log('info', \"migration cachesize: \" . render_bytes($cachesize, 1));\n    $qemu_migrate_params->{'xbzrle-cache-size'} = int($cachesize);\n\n    $self->log('info', \"set migration parameters\");\n    eval { mon_cmd($vmid, \"migrate-set-parameters\", %{$qemu_migrate_params}); };\n    $self->log('info', \"migrate-set-parameters error: $@\") if $@;\n\n    if (PVE::QemuServer::vga_conf_has_spice($conf->{vga}) && !$self->{opts}->{remote}) {\n        my $rpcenv = PVE::RPCEnvironment::get();\n        my $authuser = $rpcenv->get_user();\n\n        my $target_version = PVE::QemuServer::Helpers::get_node_pvecfg_version($self->{node});\n\n        my $ticket_port = undef;\n        # Check if target is new enough for having the port encoded in the proxy ticket.\n        if (\n            $target_version\n            && PVE::QemuServer::Helpers::pvecfg_min_version($target_version, 9, 1, 9)\n        ) {\n            $ticket_port = $spice_port;\n        }\n\n        my (undef, $proxyticket) = PVE::AccessControl::assemble_spice_ticket(\n            $authuser, $vmid, $self->{node}, $ticket_port,\n        );\n\n        my $filename = \"/etc/pve/nodes/$self->{node}/pve-ssl.pem\";\n        my $subject = PVE::AccessControl::read_x509_subject_spice($filename);\n\n        $self->log('info', \"spice client_migrate_info\");\n\n        eval {\n            mon_cmd(\n                $vmid, \"client_migrate_info\",\n                protocol => 'spice',\n                hostname => $proxyticket,\n                'port' => 0,\n                'tls-port' => $spice_port,\n                'cert-subject' => $subject,\n            );\n        };\n        $self->log('info', \"client_migrate_info error: $@\") if $@;\n\n    }\n\n    my $start = time();\n\n    $self->log('info', \"start migrate command to $migrate_uri\");\n    eval { mon_cmd($vmid, \"migrate\", uri => $migrate_uri); };\n    my $merr = $@;\n    $self->log('info', \"migrate uri => $migrate_uri failed: $merr\") if $merr;\n\n    my $last_mem_transferred = 0;\n    my $last_vfio_transferred = 0;\n    my $usleep = 1000000;\n    my $i = 0;\n    my $err_count = 0;\n    my $lastrem = undef;\n    my $downtimecounter = 0;\n    while (1) {\n        $i++;\n        my $avglstat = $last_mem_transferred ? $last_mem_transferred / $i : 0;\n\n        usleep($usleep);\n\n        my $stat = eval { mon_cmd($vmid, \"query-migrate\") };\n        if (my $err = $@) {\n            $err_count++;\n            warn \"query migrate failed: $err\\n\";\n            $self->log('info', \"query migrate failed: $err\");\n            if ($err_count <= 5) {\n                usleep(1_000_000);\n                next;\n            }\n            die \"too many query migrate failures - aborting\\n\";\n        }\n\n        my $status = $stat->{status};\n        if (defined($status) && $status =~ m/^(cancelling|setup|wait-unplug)$/im) {\n            $self->log('info', \"migration in status '$status' - waiting for transition\");\n            sleep(1);\n            next;\n        }\n\n        if (!defined($status) || $status !~ m/^(active|cancelled|completed|device|failed)$/im) {\n            die $merr if $merr;\n            die \"unable to parse migration status '$status' - aborting\\n\";\n        }\n        $merr = undef;\n        $err_count = 0;\n\n        my $memstat = $stat->{ram};\n\n        my $mem_transferred = $memstat->{transferred} || 0;\n        my $vfio_transferred = $stat->{vfio}->{transferred} || 0;\n\n        if ($status eq 'completed') {\n            my $delay = time() - $start;\n            if ($delay > 0) {\n                my $total = $memstat->{total} || 0;\n                my $avg_speed = render_bytes($total / $delay, 1);\n                my $downtime = $stat->{downtime} || 0;\n                $self->log('info', \"average migration speed: $avg_speed/s - downtime $downtime ms\");\n            }\n\n            if ($mem_transferred > 0 || $vfio_transferred > 0) {\n                my $transferred_h = render_bytes($mem_transferred, 1);\n                my $summary = \"transferred $transferred_h VM-state\";\n\n                if ($vfio_transferred > 0) {\n                    my $vfio_h = render_bytes($vfio_transferred, 1);\n                    $summary .= \" (+ $vfio_h VFIO-state)\";\n                }\n\n                $self->log('info', \"migration $status, $summary\");\n            }\n        }\n\n        if ($status eq 'failed' || $status eq 'cancelled') {\n            my $message = $stat->{'error-desc'} ? \"$status - $stat->{'error-desc'}\" : $status;\n            $self->log('info', \"migration status error: $message\");\n            die \"aborting\\n\";\n        }\n\n        if ($status ne 'active' && $status ne 'device') {\n            $self->log('info', \"migration status: $status\");\n            last;\n        }\n\n        if (\n            $mem_transferred ne $last_mem_transferred\n            || $vfio_transferred ne $last_vfio_transferred\n        ) {\n            my $rem = $memstat->{remaining} || 0;\n            my $total = $memstat->{total} || 0;\n            my $speed = ($memstat->{'pages-per-second'} // 0) * ($memstat->{'page-size'} // 0);\n            my $dirty_rate = ($memstat->{'dirty-pages-rate'} // 0) * ($memstat->{'page-size'} // 0);\n\n            # reduce sleep if remaining memory is lower than the average transfer speed\n            $usleep = 100_000 if $avglstat && $rem < $avglstat;\n\n            # also reduce logging if we poll more frequent\n            my $should_log = $usleep > 100_000 ? 1 : ($i % 10) == 0;\n\n            my $total_h = render_bytes($total, 1);\n            my $transferred_h = render_bytes($mem_transferred, 1);\n            my $speed_h = render_bytes($speed, 1);\n\n            my $progress = \"transferred $transferred_h of $total_h VM-state, ${speed_h}/s\";\n\n            if ($vfio_transferred > 0) {\n                my $vfio_h = render_bytes($vfio_transferred, 1);\n                $progress .= \" (+ $vfio_h VFIO-state)\";\n            }\n\n            if ($dirty_rate > $speed) {\n                my $dirty_rate_h = render_bytes($dirty_rate, 1);\n                $progress .= \", VM dirties lots of memory: $dirty_rate_h/s\";\n            }\n\n            $self->log('info', \"migration $status, $progress\") if $should_log;\n\n            my $xbzrle = $stat->{\"xbzrle-cache\"} || {};\n            my ($xbzrlebytes, $xbzrlepages) = $xbzrle->@{ 'bytes', 'pages' };\n            if ($xbzrlebytes || $xbzrlepages) {\n                my $bytes_h = render_bytes($xbzrlebytes, 1);\n\n                my $msg = \"send updates to $xbzrlepages pages in $bytes_h encoded memory\";\n\n                $msg .= sprintf(\", cache-miss %.2f%%\", $xbzrle->{'cache-miss-rate'} * 100)\n                    if $xbzrle->{'cache-miss-rate'};\n\n                $msg .= \", overflow $xbzrle->{overflow}\" if $xbzrle->{overflow};\n\n                $self->log('info', \"xbzrle: $msg\") if $should_log;\n            }\n\n            if (($lastrem && $rem > $lastrem) || ($rem == 0)) {\n                $downtimecounter++;\n            }\n            $lastrem = $rem;\n\n            if ($downtimecounter > 5) {\n                $downtimecounter = 0;\n                $migrate_downtime = increase_migrate_downtime($self, $vmid, $migrate_downtime);\n            }\n        }\n\n        $last_mem_transferred = $mem_transferred;\n        $last_vfio_transferred = $vfio_transferred;\n    }\n\n    if ($self->{storage_migration}) {\n        # finish block-job with block-job-cancel, to disconnect source VM from NBD\n        # to avoid it trying to re-establish it. We are in blockjob ready state,\n        # thus, this command changes to it to blockjob complete (see qapi docs)\n        eval {\n            PVE::QemuServer::BlockJob::monitor(\n                vm_qmp_peer($vmid), undef, $self->{storage_migration_jobs}, 'cancel',\n            );\n        };\n        if (my $err = $@) {\n            die \"Failed to complete storage migration: $err\\n\";\n        }\n    }\n}\n\nsub phase2_cleanup {\n    my ($self, $vmid, $err) = @_;\n\n    return if !$self->{errors};\n    $self->{phase2errors} = 1;\n\n    $self->log('info', \"aborting phase 2 - cleanup resources\");\n\n    $self->log('info', \"migrate_cancel\");\n    eval { mon_cmd($vmid, \"migrate_cancel\"); };\n    $self->log('info', \"migrate_cancel error: $@\") if $@;\n\n    my $vm_status =\n        eval { mon_cmd($vmid, 'query-status')->{status} or die \"no 'status' in result\\n\"; };\n    $self->log('err', \"query-status error: $@\") if $@;\n\n    # Can end up in POSTMIGRATE state if failure occurred after convergence. Try going back to\n    # original state. Unfortunately, direct transition from POSTMIGRATE to PAUSED is not possible.\n    if ($vm_status && $vm_status eq 'postmigrate') {\n        if (!$self->{vm_was_paused}) {\n            eval { mon_cmd($vmid, 'cont'); };\n            $self->log('err', \"resuming VM failed: $@\") if $@;\n        } else {\n            $self->log('err', \"VM was paused, but ended in postmigrate state\");\n        }\n    }\n\n    my $conf = $self->{vmconf};\n    delete $conf->{lock};\n    eval { PVE::QemuConfig->write_config($vmid, $conf) };\n    if (my $err = $@) {\n        $self->log('err', $err);\n    }\n\n    # cleanup resources on target host\n    if ($self->{storage_migration}) {\n        eval {\n            PVE::QemuServer::BlockJob::qemu_blockjobs_cancel(\n                vm_qmp_peer($vmid),\n                $self->{storage_migration_jobs},\n            );\n        };\n        if (my $err = $@) {\n            $self->log('err', $err);\n        }\n    }\n\n    eval { $self->cleanup_bitmaps() };\n    if (my $err = $@) {\n        $self->log('err', $err);\n    }\n\n    my $nodename = PVE::INotify::nodename();\n\n    if ($self->{tunnel} && $self->{tunnel}->{version} >= 2) {\n        PVE::Tunnel::write_tunnel($self->{tunnel}, 10, 'stop');\n    } else {\n        my $cmd =\n            [@{ $self->{rem_ssh} }, 'qm', 'stop', $vmid, '--skiplock', '--migratedfrom', $nodename];\n        eval {\n            PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => sub { });\n        };\n        if (my $err = $@) {\n            $self->log('err', $err);\n            $self->{errors} = 1;\n        }\n    }\n\n    # cleanup after stopping, otherwise disks might be in-use by target VM!\n    eval { PVE::QemuMigrate::cleanup_remotedisks($self) };\n    if (my $err = $@) {\n        $self->log('err', $err);\n    }\n\n    if ($self->{running} && $self->{opts}->{'with-conntrack-state'}) {\n        # if the VM is running, that means we also tried to migrate additional\n        # state via our dbus-vmstate helper\n        # only need to locally stop it, on the target the VM cleanup will\n        # handle it\n        PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($vmid);\n    }\n\n    if ($self->{tunnel}) {\n        eval { PVE::Tunnel::finish_tunnel($self->{tunnel}); };\n        if (my $err = $@) {\n            $self->log('err', $err);\n            $self->{errors} = 1;\n        }\n    }\n}\n\nsub phase3 {\n    my ($self, $vmid) = @_;\n\n    return;\n}\n\nsub phase3_cleanup {\n    my ($self, $vmid, $err) = @_;\n\n    my $conf = $self->{vmconf};\n    return if $self->{phase2errors};\n\n    my $tunnel = $self->{tunnel};\n\n    # we'll need an unmodified copy of the config later for the cleanup\n    my $oldconf = dclone($conf);\n\n    if ($self->{volume_map} && !$self->{opts}->{remote}) {\n        my $target_drives = $self->{target_drive};\n\n        # FIXME: for NBD storage migration we now only update the volid, and\n        # not the full drivestr from the target node. Workaround that until we\n        # got some real rescan, to avoid things like wrong format in the drive\n        delete $conf->{$_} for keys %$target_drives;\n        PVE::QemuConfig->update_volume_ids($conf, $self->{volume_map});\n\n        for my $drive (keys %$target_drives) {\n            $conf->{$drive} = $target_drives->{$drive}->{drivestr};\n        }\n        PVE::QemuConfig->write_config($vmid, $conf);\n    }\n\n    # transfer replication state before move config\n    if (!$self->{opts}->{remote}) {\n        $self->transfer_replication_state() if $self->{is_replicated};\n        PVE::QemuConfig->move_config_to_node($vmid, $self->{node});\n        $self->switch_replication_job_target() if $self->{is_replicated};\n    }\n\n    if ($self->{livemigration}) {\n        if ($self->{stopnbd}) {\n            $self->log('info', \"stopping NBD storage migration server on target.\");\n            # stop nbd server on remote vm - requirement for resume since 2.9\n            if ($tunnel && $tunnel->{version} && $tunnel->{version} >= 2) {\n                eval { PVE::Tunnel::write_tunnel($tunnel, 30, 'nbdstop'); };\n                if (my $err = $@) {\n                    $self->log('err', $err);\n                    $self->{errors} = 1;\n                }\n            } else {\n                my $cmd = [@{ $self->{rem_ssh} }, 'qm', 'nbdstop', $vmid];\n\n                eval {\n                    PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => sub { });\n                };\n                if (my $err = $@) {\n                    $self->log('err', $err);\n                    $self->{errors} = 1;\n                }\n            }\n        }\n\n        # deletes local FDB entries if learning is disabled, they'll be re-added on target on resume\n        PVE::QemuServer::Network::del_nets_bridge_fdb($conf, $vmid);\n\n        if (!$self->{vm_was_paused}) {\n            # config moved and nbd server stopped - now we can resume vm on target\n            if ($tunnel && $tunnel->{version} && $tunnel->{version} >= 1) {\n                my $cmd = $tunnel->{version} == 1 ? \"resume $vmid\" : \"resume\";\n                eval { PVE::Tunnel::write_tunnel($tunnel, 30, $cmd); };\n                if (my $err = $@) {\n                    $self->log('err', $err);\n                    $self->{errors} = 1;\n                }\n            } else {\n                # nocheck in case target node hasn't processed the config move/rename yet\n                my $cmd = [@{ $self->{rem_ssh} }, 'qm', 'resume', $vmid, '--skiplock', '--nocheck'];\n                my $logf = sub {\n                    my $line = shift;\n                    $self->log('err', $line);\n                };\n                eval {\n                    PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => $logf);\n                };\n                if (my $err = $@) {\n                    $self->log('err', $err);\n                    $self->{errors} = 1;\n                }\n            }\n        }\n\n        if (\n            $self->{storage_migration}\n            && PVE::QemuServer::Agent::get_qga_key($conf, 'fstrim_cloned_disks')\n            && $self->{running}\n        ) {\n            if (!$self->{vm_was_paused}) {\n                $self->log('info', \"issuing guest fstrim\");\n                if ($self->{opts}->{remote}) {\n                    PVE::Tunnel::write_tunnel($self->{tunnel}, 600, 'fstrim');\n                } else {\n                    my $cmd = [@{ $self->{rem_ssh} }, 'qm', 'guest', 'cmd', $vmid, 'fstrim'];\n                    eval {\n                        PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => sub { });\n                    };\n                    if (my $err = $@) {\n                        $self->log('err', \"fstrim failed - $err\");\n                        $self->{errors} = 1;\n                    }\n                }\n            } else {\n                $self->log('info', \"skipping guest fstrim, because VM is paused\");\n            }\n        }\n\n        if ($self->{running} && $self->{opts}->{'with-conntrack-state'}) {\n            # if the VM is running, that means we also migrated additional\n            # state via our dbus-vmstate helper\n            $self->log('info', 'stopping migration dbus-vmstate helpers');\n\n            # first locally\n            my $num = PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($vmid);\n            if (defined($num)) {\n                my $plural = $num != 1 ? \"entries\" : \"entry\";\n                $self->log('info', \"migrated $num conntrack state $plural\");\n            }\n\n            # .. and then remote\n            my $targetnode = $self->{node};\n            eval {\n                # FIXME: introduce proper way to call API methods on another node?\n                # See also e.g. pve-network/src/PVE/API2/Network/SDN.pm, which\n                # does something similar.\n                PVE::Tools::run_command([\n                    'pvesh',\n                    'create',\n                    \"/nodes/$targetnode/qemu/$vmid/dbus-vmstate\",\n                    '--action',\n                    'stop',\n                ]);\n            };\n            if (my $err = $@) {\n                $self->log('warn', \"failed to stop dbus-vmstate on $targetnode: $err\\n\");\n            }\n\n            # also flush now-old local conntrack entries for the migrated VM\n            $self->log('info', 'flushing conntrack state for guest on source node');\n            PVE::Firewall::Helpers::flush_fw_ct_entries_by_mark($vmid);\n        }\n    }\n\n    # close tunnel on successful migration, on error phase2_cleanup closed it\n    if ($tunnel && $tunnel->{version} == 1) {\n        eval { PVE::Tunnel::finish_tunnel($tunnel); };\n        if (my $err = $@) {\n            $self->log('err', $err);\n            $self->{errors} = 1;\n        }\n        $tunnel = undef;\n        delete $self->{tunnel};\n    }\n\n    eval {\n        my $timer = 0;\n        if (PVE::QemuServer::vga_conf_has_spice($conf->{vga}) && $self->{running}) {\n            $self->log('info', \"Waiting for spice server migration\");\n            while (1) {\n                my $res = mon_cmd($vmid, 'query-spice');\n                last if int($res->{'migrated'}) == 1;\n                last if $timer > 50;\n                $timer++;\n                usleep(200000);\n            }\n        }\n    };\n\n    # always stop local VM with nocheck, since config is moved already\n    eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1); };\n    if (my $err = $@) {\n        $self->log('err', \"stopping vm failed - $err\");\n        $self->{errors} = 1;\n    }\n\n    # stop with nocheck does not do a cleanup, so do it here with the original config\n    eval { PVE::QemuServer::vm_stop_cleanup($self->{storecfg}, $vmid, $oldconf) };\n    if (my $err = $@) {\n        $self->log('err', \"Cleanup after stopping VM failed - $err\");\n        $self->{errors} = 1;\n    }\n\n    my @not_replicated_volumes = $self->filter_local_volumes(undef, 0);\n\n    # destroy local copies\n    foreach my $volid (@not_replicated_volumes) {\n        # remote is cleaned up below\n        next if $self->{opts}->{remote};\n\n        eval { PVE::Storage::vdisk_free($self->{storecfg}, $volid); };\n        if (my $err = $@) {\n            $self->log('err', \"removing local copy of '$volid' failed - $err\");\n            $self->{errors} = 1;\n            last if $err =~ /^interrupted by signal$/;\n        }\n    }\n\n    # clear migrate lock\n    if ($tunnel && $tunnel->{version} >= 2) {\n        PVE::Tunnel::write_tunnel($tunnel, 10, \"unlock\");\n\n        PVE::Tunnel::finish_tunnel($tunnel);\n    } else {\n        my $cmd = [@{ $self->{rem_ssh} }, 'qm', 'unlock', $vmid];\n        $self->cmd_logerr($cmd, errmsg => \"failed to clear migrate lock\");\n    }\n\n    if ($self->{opts}->{remote} && $self->{opts}->{delete}) {\n        eval { PVE::QemuServer::destroy_vm($self->{storecfg}, $vmid, 1, undef, 0) };\n        warn \"Failed to remove source VM - $@\\n\" if $@;\n    }\n}\n\nsub final_cleanup {\n    my ($self, $vmid) = @_;\n\n    # nothing to do\n}\n\nsub round_powerof2 {\n    return 1 if $_[0] < 2;\n    return 2 << int(log($_[0] - 1) / log(2));\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Agent.pm",
    "content": "package PVE::QemuServer::Agent;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse MIME::Base64 qw(decode_base64 encode_base64);\n\nuse PVE::JSONSchema;\n\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Monitor;\n\nuse base 'Exporter';\n\nour @EXPORT_OK = qw(\n    check_agent_error\n    agent_cmd\n    get_qga_key\n    parse_guest_agent\n    qga_check_running\n);\n\nour $agent_fmt = {\n    enabled => {\n        description =>\n            \"Enable/disable communication with a QEMU Guest Agent (QGA) running in the VM.\",\n        type => 'boolean',\n        default => 0,\n        default_key => 1,\n    },\n    fstrim_cloned_disks => {\n        description => \"Run fstrim after moving a disk or migrating the VM.\",\n        type => 'boolean',\n        optional => 1,\n        default => 0,\n    },\n    # keep for old backup restore compatibility\n    'freeze-fs-on-backup' => { alias => 'freeze-fs' },\n    # TODO: was only on test repo, drop with PVE 10.\n    'guest-fsfreeze' => { alias => 'freeze-fs' },\n    'freeze-fs' => {\n        description => \"Freeze guest filesystems through QGA for consistent disk state on\"\n            . \" operations such as snapshots, backups, replications and clones.\",\n        verbose_description =>\n            \"Whether to issue the guest-fsfreeze-freeze and guest-fsfreeze-thaw QEMU guest agent\"\n            . \" commands. Backups in snapshot mode, clones, snapshots without RAM, importing\"\n            . \" disks from a running guest, and replications normally issue a guest-fsfreeze-freeze\"\n            . \" and a respective thaw command when the QEMU Guest agent option is enabled in the\"\n            . \" guest's configuration and the agent is running inside of the guest.\\n\\nThe deprecated\"\n            . \" 'freeze-fs-on-backup' setting is treated as an alias for this setting.\",\n        type => 'boolean',\n        optional => 1,\n        default => 1,\n    },\n    type => {\n        description => \"Select the agent type\",\n        type => 'string',\n        default => 'virtio',\n        optional => 1,\n        enum => [qw(virtio isa)],\n    },\n};\n\nsub parse_guest_agent {\n    my ($conf) = @_;\n\n    return {} if !defined($conf->{agent});\n\n    my $res = eval { PVE::JSONSchema::parse_property_string($agent_fmt, $conf->{agent}) };\n    warn $@ if $@;\n\n    # if the agent is disabled ignore the other potentially set properties\n    return {} if !$res->{enabled};\n    return $res;\n}\n\nsub get_qga_key {\n    my ($conf, $key) = @_;\n    return undef if !defined($conf->{agent});\n\n    my $agent = parse_guest_agent($conf);\n    return $agent->{$key};\n}\n\nsub qga_check_running {\n    my ($vmid, $nowarn) = @_;\n\n    eval { PVE::QemuServer::Monitor::mon_cmd($vmid, \"guest-ping\", timeout => 3); };\n    if ($@) {\n        warn \"QEMU Guest Agent is not running - $@\" if !$nowarn;\n        return 0;\n    }\n    return 1;\n}\n\nsub check_agent_error {\n    my ($result, $errmsg, $noerr) = @_;\n\n    $errmsg //= '';\n    my $error = '';\n    if (ref($result) eq 'HASH' && $result->{error} && $result->{error}->{desc}) {\n        $error = \"Agent error: $result->{error}->{desc}\\n\";\n    } elsif (!defined($result)) {\n        $error = \"Agent error: $errmsg\\n\";\n    }\n\n    if ($error) {\n        die $error if !$noerr;\n\n        warn $error;\n        return;\n    }\n\n    return 1;\n}\n\nsub assert_agent_available {\n    my ($vmid, $conf) = @_;\n\n    die \"No QEMU guest agent configured\\n\" if !defined($conf->{agent});\n    die \"VM $vmid is not running\\n\" if !PVE::QemuServer::Helpers::vm_running_locally($vmid);\n    die \"QEMU guest agent is not running\\n\" if !qga_check_running($vmid, 1);\n}\n\n# loads config, checks if available, executes command, checks for errors\nsub agent_cmd {\n    my ($vmid, $conf, $cmd, $params, $errormsg) = @_;\n\n    assert_agent_available($vmid, $conf);\n\n    my $res = PVE::QemuServer::Monitor::mon_cmd($vmid, \"guest-$cmd\", %$params);\n    check_agent_error($res, $errormsg);\n\n    return $res;\n}\n\nsub qemu_exec {\n    my ($vmid, $conf, $input_data, $cmd) = @_;\n\n    my $args = {\n        'capture-output' => JSON::true,\n    };\n\n    if ($cmd) {\n        $args->{path} = shift @$cmd;\n        $args->{arg} = $cmd;\n    }\n\n    $args->{'input-data'} = encode_base64($input_data, '') if defined($input_data);\n\n    die \"command or input-data (or both) required\\n\"\n        if !defined($args->{'input-data'}) && !defined($args->{path});\n\n    my $errmsg = \"can't execute command\";\n    if ($cmd) {\n        $errmsg .= \" ($args->{path} $args->{arg})\";\n    }\n    if (defined($input_data)) {\n        $errmsg .= \" (input-data given)\";\n    }\n\n    my $res = agent_cmd($vmid, $conf, \"exec\", $args, $errmsg);\n\n    return $res;\n}\n\nsub qemu_exec_status {\n    my ($vmid, $conf, $pid) = @_;\n\n    my $res =\n        agent_cmd($vmid, $conf, \"exec-status\", { pid => $pid }, \"can't get exec status for '$pid'\");\n\n    if ($res->{'out-data'}) {\n        my $decoded = eval { decode_base64($res->{'out-data'}) };\n        warn $@ if $@;\n        if (defined($decoded)) {\n            $res->{'out-data'} = $decoded;\n        }\n    }\n\n    if ($res->{'err-data'}) {\n        my $decoded = eval { decode_base64($res->{'err-data'}) };\n        warn $@ if $@;\n        if (defined($decoded)) {\n            $res->{'err-data'} = $decoded;\n        }\n    }\n\n    # convert JSON::Boolean to 1/0\n    foreach my $d (keys %$res) {\n        if (JSON::is_bool($res->{$d})) {\n            $res->{$d} = ($res->{$d}) ? 1 : 0;\n        }\n    }\n\n    return $res;\n}\n\n=head3 should_fs_freeze\n\nReturns whether guest filesystem freeze/thaw should be attempted based on the agent configuration.\nDoes B<not> check whether the agent is actually running.\n\n=cut\n\nsub should_fs_freeze {\n    my ($conf) = @_;\n\n    my $agent = parse_guest_agent($conf);\n    return 0 if !$agent->{enabled};\n    return $agent->{'freeze-fs'} // 1;\n}\n\n=head3 guest_fsfreeze\n\n    guest_fsfreeze($vmid);\n\nFreeze the file systems of the guest C<$vmid>. Check that the guest agent is enabled and running\nbefore calling this function. Dies if the file systems cannot be frozen.\n\nWith C<mon_cmd()>, it can happen that a guest agent command is read, but then the guest agent never\nsends an answer, because the service in the guest is stopped/killed. For example, if a guest reboot\nhappens before the command can be successfully executed. This is usually not problematic, but the\nfsfreeze-freeze command should use a timeout of 1 hour, so the guest agent socket would be blocked\nfor that amount of time, waiting on a command that is not being executed anymore.\n\nThis function uses a lower timeout for the initial fsfreeze-freeze command, and issues an\nfsfreeze-status command afterwards, which will return immediately if the fsfreeze-freeze command\nalready finished, and which will be queued if not. This is used as a proxy to determine whether the\nfsfreeze-freeze command is still running and to check whether it was successful. Using a too low\ntimeout would mean stuffing/queuing many fsfreeze-status commands while the guest agent might still\nbe busy actually doing the freeze. In total, fsfreeze-freeze is still allowed to take 1 hour, but\nthe time the socket is blocked after a lost command is at most 10 minutes.\n\n=cut\n\nsub guest_fsfreeze {\n    my ($vmid) = @_;\n\n    my $timeout = 10 * 60;\n\n    my $result = eval {\n        PVE::QemuServer::Monitor::mon_cmd($vmid, 'guest-fsfreeze-freeze', timeout => $timeout);\n    };\n    if ($result && ref($result) eq 'HASH' && $result->{error}) {\n        my $error = $result->{error}->{desc} // 'unknown';\n        die \"unable to freeze guest fs - $error\\n\";\n    } elsif (defined($result)) {\n        return; # command successful\n    }\n\n    my $status;\n    eval {\n        my ($i, $last_iteration) = (0, 5);\n        while ($i < $last_iteration && !defined($status)) {\n            print \"still waiting on guest fs freeze - timeout in \"\n                . ($timeout * ($last_iteration - $i) / 60)\n                . \" minutes\\n\";\n            $i++;\n\n            $status = PVE::QemuServer::Monitor::mon_cmd(\n                $vmid, 'guest-fsfreeze-status',\n                timeout => $timeout,\n                noerr => 1,\n            );\n\n            if ($status && ref($status) eq 'HASH' && $status->{'error-is-timeout'}) {\n                $status = undef;\n            } else {\n                check_agent_error($status, 'unknown error');\n            }\n        }\n        if (!defined($status)) {\n            die \"timeout after \" . ($timeout * ($last_iteration + 1) / 60) . \" minutes\\n\";\n        }\n    };\n    die \"querying status after freezing guest fs failed - $@\" if $@;\n\n    die \"unable to freeze guest fs - unexpected status '$status'\\n\" if $status ne 'frozen';\n}\n\n=head3 guest_fsthaw\n\n    guest_fsthaw($vmid);\n\nThaws the file systems of the guest C<$vmid>. Dies if the file systems cannot be thawed.\n\nSee C<$guest_fsfreeze> for more details.\n\n=cut\n\nsub guest_fsthaw {\n    my ($vmid) = @_;\n\n    my $res = PVE::QemuServer::Monitor::mon_cmd($vmid, \"guest-fsfreeze-thaw\");\n    check_agent_error($res, \"unable to thaw guest filesystem\");\n\n    return;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/BlockJob.pm",
    "content": "package PVE::QemuServer::BlockJob;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse Storable qw(dclone);\n\nuse PVE::Format qw(render_duration render_bytes);\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Storage;\n\nuse PVE::QemuServer::Agent qw(qga_check_running);\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::Drive qw(checked_volume_format);\nuse PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);\nuse PVE::QemuServer::RunState;\n\n# If the job was started with auto-dismiss=false, it's necessary to dismiss it manually. Using this\n# option is useful to get the error for failed jobs here. QEMU's job lock should make it impossible\n# to see a job in 'concluded' state when auto-dismiss=true.\n# $qmp_info is the 'BlockJobInfo' for the job returned by query-block-jobs.\n# $job is the information about the job recorded on the PVE-side.\n# A block node $job->{'detach-node-name'} will be detached if present.\nsub qemu_handle_concluded_blockjob {\n    my ($qmp_peer, $job_id, $qmp_info, $job) = @_;\n\n    eval { qmp_cmd($qmp_peer, 'job-dismiss', id => $job_id); };\n    log_warn(\"$job_id: failed to dismiss job - $@\") if $@;\n\n    # If there was an error or if the job was cancelled, always detach the target. This is correct\n    # even when the job was cancelled after completion, because then the disk is not switched over\n    # to use the target.\n    $job->{'detach-node-name'} = $job->{'target-node-name'} if $qmp_info->{error} || $job->{cancel};\n\n    if (my $node_name = $job->{'detach-node-name'}) {\n        eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $node_name); };\n        log_warn($@) if $@;\n    }\n\n    die \"$job_id: $qmp_info->{error} (io-status: $qmp_info->{'io-status'})\\n\" if $qmp_info->{error};\n}\n\nsub qemu_blockjobs_cancel {\n    my ($qmp_peer, $jobs) = @_;\n\n    foreach my $job (keys %$jobs) {\n        print \"$job: Cancelling block job\\n\";\n        eval { qmp_cmd($qmp_peer, \"block-job-cancel\", device => $job); };\n        $jobs->{$job}->{cancel} = 1;\n    }\n\n    while (1) {\n        my $stats = qmp_cmd($qmp_peer, \"query-block-jobs\");\n\n        my $running_jobs = {};\n        foreach my $stat (@$stats) {\n            $running_jobs->{ $stat->{device} } = $stat;\n        }\n\n        foreach my $job (keys %$jobs) {\n            my $info = $running_jobs->{$job};\n            eval {\n                qemu_handle_concluded_blockjob($qmp_peer, $job, $info, $jobs->{$job})\n                    if $info && $info->{status} eq 'concluded';\n            };\n            log_warn($@) if $@; # only warn and proceed with canceling other jobs\n\n            if (defined($jobs->{$job}->{cancel}) && !defined($info)) {\n                print \"$job: Done.\\n\";\n                delete $jobs->{$job};\n            }\n        }\n\n        last if scalar(keys %$jobs) == 0;\n\n        sleep 1;\n    }\n}\n\n# $completion can be either\n# 'complete': wait until all jobs are ready, job-complete them (default)\n# 'cancel': wait until all jobs are ready, block-job-cancel them\n# 'skip': wait until all jobs are ready, return with block jobs in ready state\n# 'auto': wait until all jobs disappear, only use for jobs which complete automatically\nsub monitor {\n    my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_;\n\n    die \"drive mirror: different destination is only supported when peer is main QEMU instance\\n\"\n        if $vmiddst && $qmp_peer->{type} ne 'qmp';\n\n    $completion //= 'complete';\n    $op //= \"mirror\";\n\n    eval {\n        my $err_complete = 0;\n\n        my $starttime = time();\n        while (1) {\n            die \"block job ('$op') timed out\\n\" if $err_complete > 300;\n\n            my $stats = qmp_cmd($qmp_peer, \"query-block-jobs\");\n            my $ctime = time();\n\n            my $running_jobs = {};\n            for my $stat (@$stats) {\n                next if $stat->{type} ne $op;\n                $running_jobs->{ $stat->{device} } = $stat;\n            }\n\n            my $readycounter = 0;\n\n            for my $job_id (sort keys %$jobs) {\n                my $job = $running_jobs->{$job_id};\n\n                my $vanished = !defined($job);\n                my $complete = defined($jobs->{$job_id}->{complete}) && $vanished;\n                if ($complete || ($vanished && $completion eq 'auto')) {\n                    print \"$job_id: $op-job finished\\n\";\n                    delete $jobs->{$job_id};\n                    next;\n                }\n\n                die \"$job_id: '$op' has been cancelled\\n\" if !defined($job);\n\n                if ($job && $job->{status} eq 'concluded') {\n                    qemu_handle_concluded_blockjob($qmp_peer, $job_id, $job, $jobs->{$job_id});\n                }\n\n                my $busy = $job->{busy};\n                my $ready = $job->{ready};\n                if (my $total = $job->{len}) {\n                    my $transferred = $job->{offset} || 0;\n                    my $remaining = $total - $transferred;\n                    my $percent = sprintf \"%.2f\", ($transferred * 100 / $total);\n\n                    my $duration = $ctime - $starttime;\n                    my $total_h = render_bytes($total, 1);\n                    my $transferred_h = render_bytes($transferred, 1);\n\n                    my $status = sprintf(\n                        \"transferred $transferred_h of $total_h ($percent%%) in %s\",\n                        render_duration($duration),\n                    );\n\n                    if ($ready) {\n                        if ($busy) {\n                            $status .= \", still busy\"; # shouldn't even happen? but mirror is weird\n                        } else {\n                            $status .= \", ready\";\n                        }\n                    }\n                    print \"$job_id: $status\\n\" if !$jobs->{$job_id}->{ready};\n                    $jobs->{$job_id}->{ready} = $ready;\n                }\n\n                $readycounter++ if $job->{ready};\n            }\n\n            last if scalar(keys %$jobs) == 0;\n\n            if ($readycounter == scalar(keys %$jobs)) {\n                print \"all '$op' jobs are ready\\n\";\n\n                # do the complete later (or has already been done)\n                last if $completion eq 'skip' || $completion eq 'auto';\n\n                if ($qmp_peer->{type} eq 'qmp' && $vmiddst && $vmiddst != $qmp_peer->{id}) {\n                    my $vmid = $qmp_peer->{id};\n                    my $should_fsfreeze = $qga && qga_check_running($vmid);\n                    if ($should_fsfreeze) {\n                        print \"issuing guest agent 'guest-fsfreeze-freeze' command\\n\";\n                        eval { PVE::QemuServer::Agent::guest_fsfreeze($vmid); };\n                        warn $@ if $@;\n                    } else {\n                        if (!$qga) {\n                            print \"skipping guest-agent 'guest-fsfreeze-freeze', disabled in VM\"\n                                . \" options\\n\";\n                        } else {\n                            print \"skipping guest agent 'guest-fsfreeze-freeze' command: the\"\n                                . \" agent is not running inside of the guest\\n\";\n                        }\n                        print \"suspend vm\\n\";\n                        eval { PVE::QemuServer::RunState::vm_suspend($vmid, 1); };\n                        warn $@ if $@;\n                    }\n\n                    # if we clone a disk for a new target vm, we don't switch the disk\n                    qemu_blockjobs_cancel($qmp_peer, $jobs);\n\n                    if ($should_fsfreeze) {\n                        print \"issuing guest agent 'guest-fsfreeze-thaw' command\\n\";\n                        eval { PVE::QemuServer::Agent::guest_fsthaw($vmid); };\n                        warn $@ if $@;\n                    } else {\n                        print \"resume vm\\n\";\n                        eval { PVE::QemuServer::RunState::vm_resume($vmid, 1, 1); };\n                        warn $@ if $@;\n                    }\n\n                    last;\n                } else {\n\n                    for my $job_id (sort keys %$jobs) {\n                        # try to switch the disk if source and destination are on the same guest\n                        print \"$job_id: Completing block job...\\n\";\n\n                        # For blockdev, need to detach appropriate node. QEMU will only drop it if\n                        # it was implicitly added (e.g. as the child of a top throttle node), but\n                        # not if it was explicitly added via blockdev-add (e.g. as a previous mirror\n                        # target).\n                        my $detach_node_name;\n                        eval {\n                            if ($completion eq 'complete') {\n                                $detach_node_name = $jobs->{$job_id}->{'source-node-name'};\n                                qmp_cmd($qmp_peer, 'job-complete', id => $job_id);\n                            } elsif ($completion eq 'cancel') {\n                                $detach_node_name = $jobs->{$job_id}->{'target-node-name'};\n                                qmp_cmd($qmp_peer, 'block-job-cancel', device => $job_id);\n                            } else {\n                                die \"invalid completion value: $completion\\n\";\n                            }\n                        };\n                        my $err = $@;\n                        if ($err && $err =~ m/cannot be completed/) {\n                            print \"$job_id: block job cannot be completed, trying again.\\n\";\n                            $err_complete++;\n                        } elsif ($err) {\n                            die \"$job_id: block job cannot be completed - $err\\n\";\n                        } else {\n                            $jobs->{$job_id}->{'detach-node-name'} = $detach_node_name\n                                if $detach_node_name;\n\n                            print \"$job_id: Completed successfully.\\n\";\n                            $jobs->{$job_id}->{complete} = 1;\n                        }\n                    }\n                }\n            }\n            sleep 1;\n        }\n    };\n    my $err = $@;\n\n    if ($err) {\n        eval { qemu_blockjobs_cancel($qmp_peer, $jobs) };\n        die \"block job ($op) error: $err\";\n    }\n}\n\nmy sub common_mirror_qmp_options {\n    my ($device_id, $qemu_target, $src_bitmap, $bwlimit) = @_;\n\n    my $opts = {\n        timeout => 10,\n        device => \"$device_id\",\n        sync => \"full\",\n        target => $qemu_target,\n        'auto-dismiss' => JSON::false,\n    };\n\n    if (defined($src_bitmap)) {\n        $opts->{sync} = 'incremental';\n        $opts->{bitmap} = $src_bitmap;\n        print \"drive mirror re-using dirty bitmap '$src_bitmap'\\n\";\n    }\n\n    if (defined($bwlimit)) {\n        $opts->{speed} = $bwlimit * 1024;\n        print \"drive mirror is starting for $device_id with bandwidth limit: ${bwlimit} KB/s\\n\";\n    } else {\n        print \"drive mirror is starting for $device_id\\n\";\n    }\n\n    return $opts;\n}\n\nsub qemu_drive_mirror {\n    my (\n        $vmid,\n        $drive_id,\n        $dst_volid,\n        $vmiddst,\n        $is_zero_initialized,\n        $jobs,\n        $completion,\n        $qga,\n        $bwlimit,\n        $src_bitmap,\n    ) = @_;\n\n    my $device_id = \"drive-$drive_id\";\n\n    $jobs = {} if !$jobs;\n\n    my $qemu_target;\n    my $format;\n    $jobs->{$device_id} = {};\n\n    if ($dst_volid =~ /^nbd:/) {\n        $qemu_target = $dst_volid;\n        $format = \"nbd\";\n    } else {\n        my $storecfg = PVE::Storage::config();\n\n        $format = checked_volume_format($storecfg, $dst_volid);\n\n        my $dst_path = PVE::Storage::path($storecfg, $dst_volid);\n\n        $qemu_target = $is_zero_initialized ? \"zeroinit:$dst_path\" : $dst_path;\n    }\n\n    my $opts = common_mirror_qmp_options($device_id, $qemu_target, $src_bitmap, $bwlimit);\n    $opts->{mode} = \"existing\";\n    $opts->{format} = $format if $format;\n\n    # if a job already runs for this device we get an error, catch it for cleanup\n    eval { mon_cmd($vmid, \"drive-mirror\", %$opts); };\n    if (my $err = $@) {\n        eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };\n        warn \"$@\\n\" if $@;\n        die \"mirroring error: $err\\n\";\n    }\n\n    monitor(vm_qmp_peer($vmid), $vmiddst, $jobs, $completion, $qga);\n}\n\n# Callers should version guard this (only available with a binary >= QEMU 8.2)\nsub qemu_drive_mirror_switch_to_active_mode {\n    my ($vmid, $jobs) = @_;\n\n    my $switching = {};\n\n    for my $job (sort keys $jobs->%*) {\n        print \"$job: switching to actively synced mode\\n\";\n\n        eval {\n            mon_cmd(\n                $vmid,\n                \"block-job-change\",\n                id => $job,\n                type => 'mirror',\n                'copy-mode' => 'write-blocking',\n            );\n            $switching->{$job} = 1;\n        };\n        die \"could not switch mirror job $job to active mode - $@\\n\" if $@;\n    }\n\n    while (1) {\n        my $stats = mon_cmd($vmid, \"query-block-jobs\");\n\n        my $running_jobs = {};\n        $running_jobs->{ $_->{device} } = $_ for $stats->@*;\n\n        for my $job (sort keys $switching->%*) {\n            die \"$job: vanished while switching to active mode\\n\" if !$running_jobs->{$job};\n\n            my $info = $running_jobs->{$job};\n            if ($info->{status} eq 'concluded') {\n                qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job});\n                # The 'concluded' state should occur here if and only if the job failed, so the\n                # 'die' below should be unreachable, but play it safe.\n                die \"$job: expected job to have failed, but no error was set\\n\";\n            }\n\n            if ($info->{'actively-synced'}) {\n                print \"$job: successfully switched to actively synced mode\\n\";\n                delete $switching->{$job};\n            }\n        }\n\n        last if scalar(keys $switching->%*) == 0;\n\n        sleep 1;\n    }\n}\n\n=pod\n\n=head3 blockdev_mirror\n\n    blockdev_mirror($source, $dest, $jobs, $completion, $options)\n\nMirrors the volume of a running VM specified by C<$source> to destination C<$dest>.\n\n=over\n\n=item C<$source>: The source information consists of:\n\n=over\n\n=item C<< $source->{vmid} >>: The ID of the running VM the source volume belongs to.\n\n=item C<< $source->{drive} >>: The drive configuration of the source volume as currently attached to\nthe VM.\n\n=item C<< $source->{bitmap} >>: (optional) Use incremental mirroring based on the specified bitmap.\n\n=back\n\n=item C<$dest>: The destination information consists of:\n\n=over\n\n=item C<< $dest->{volid} >>: The volume ID of the target volume.\n\n=item C<< $dest->{vmid} >>: (optional) The ID of the VM the target volume belongs to. Defaults to\nC<< $source->{vmid} >>.\n\n=item C<< $dest->{'zero-initialized'} >>: (optional) True, if the target volume is zero-initialized.\n\n=back\n\n=item C<$jobs>: (optional) Other jobs in the transaction when multiple volumes should be mirrored.\nAll jobs must be ready before completion can happen.\n\n=item C<$completion>: Completion mode, default is C<complete>:\n\n=over\n\n=item C<complete>: Wait until all jobs are ready, job-complete them (default). This means switching\nthe original drive to use the new target.\n\n=item C<cancel>: Wait until all jobs are ready, block-job-cancel them. This means not switching the\noriginal drive to use the new target.\n\n=item C<skip>: Wait until all jobs are ready, return with block jobs in ready state.\n\n=item C<auto>: Wait until all jobs disappear, only use for jobs which complete automatically.\n\n=back\n\n=item C<$options>: Further options:\n\n=over\n\n=item C<< $options->{'guest-agent'} >>: If the guest agent is configured for the VM. It will be used\nto freeze and thaw the filesystems for consistency when the target belongs to a different VM.\n\n=item C<< $options->{'bwlimit'} >>: The bandwidth limit to use for the mirroring operation, in\nKiB/s.\n\n=back\n\n=back\n\n=cut\n\nsub blockdev_mirror {\n    my ($source, $dest, $jobs, $completion, $options) = @_;\n\n    my $vmid = $source->{vmid};\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($source->{drive});\n    my $device_id = \"drive-$drive_id\";\n\n    my $storecfg = PVE::Storage::config();\n\n    # Need to replace the node below the top node. This is not necessarily a format node, for\n    # example, it can also be a zeroinit node by a previous mirror! So query QEMU itself.\n    my $source_node_name =\n        PVE::QemuServer::Blockdev::get_node_name_below_throttle(vm_qmp_peer($vmid), $device_id, 1);\n\n    # Copy original drive config (aio, cache, discard, ...):\n    my $dest_drive = dclone($source->{drive});\n    delete($dest_drive->{format}); # cannot use the source's format\n    $dest_drive->{file} = $dest->{volid};\n\n    # Mirror happens below the throttle filter, so if the target is for the same VM, it will end up\n    # below the source's throttle filter, which is inserted for the drive device.\n    my $attach_dest_opts = { 'no-throttle' => 1 };\n    $attach_dest_opts->{'zero-initialized'} = 1 if $dest->{'zero-initialized'};\n\n    # Source and target need to have the exact same virtual size, see bug #3227.\n    # However, it won't be possible to resize a disk with 'size' explicitly set afterwards, so only\n    # set it for EFI disks.\n    if ($drive_id eq 'efidisk0' && !PVE::QemuServer::Blockdev::is_nbd($dest_drive)) {\n        my ($storeid) = PVE::Storage::parse_volume_id($dest_drive->{file}, 1);\n        if (\n            $storeid\n            && PVE::QemuServer::Drive::checked_volume_format($storecfg, $dest->{volid}) eq 'raw'\n        ) {\n            my $block_info = PVE::QemuServer::Blockdev::get_block_info($vmid);\n            if (my $size = $block_info->{$drive_id}->{inserted}->{image}->{'virtual-size'}) {\n                $attach_dest_opts->{size} = $size;\n            } else {\n                log_warn(\"unable to determine source block node size - continuing anyway\");\n            }\n        }\n    }\n\n    # Note that if 'aio' is not explicitly set, i.e. default, it can change if source and target\n    # don't both allow or both not allow 'io_uring' as the default.\n    my ($target_node_name) =\n        PVE::QemuServer::Blockdev::attach($storecfg, $vmid, $dest_drive, $attach_dest_opts);\n\n    $jobs = {} if !$jobs;\n    my $jobid = \"mirror-$drive_id\";\n    $jobs->{$jobid} = {\n        'source-node-name' => $source_node_name,\n        'target-node-name' => $target_node_name,\n    };\n\n    my $qmp_opts = common_mirror_qmp_options(\n        $device_id, $target_node_name, $source->{bitmap}, $options->{bwlimit},\n    );\n\n    $qmp_opts->{'job-id'} = \"$jobid\";\n    $qmp_opts->{replaces} = \"$source_node_name\";\n\n    # if a job already runs for this device we get an error, catch it for cleanup\n    eval { mon_cmd($vmid, \"blockdev-mirror\", $qmp_opts->%*); };\n    if (my $err = $@) {\n        eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };\n        log_warn(\"unable to cancel block jobs - $@\");\n        eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $target_node_name); };\n        log_warn(\"unable to delete blockdev '$target_node_name' - $@\");\n        die \"error starting blockdev mirrror - $err\";\n    }\n    monitor(\n        vm_qmp_peer($vmid),\n        $dest->{vmid},\n        $jobs,\n        $completion,\n        $options->{'guest-agent'},\n        'mirror',\n    );\n}\n\nsub mirror {\n    my ($source, $dest, $jobs, $completion, $options) = @_;\n\n    # for the switch to -blockdev\n    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($source->{vmid});\n    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {\n        blockdev_mirror($source, $dest, $jobs, $completion, $options);\n    } else {\n        my $drive_id = PVE::QemuServer::Drive::get_drive_id($source->{drive});\n        qemu_drive_mirror(\n            $source->{vmid},\n            $drive_id,\n            $dest->{volid},\n            $dest->{vmid},\n            $dest->{'zero-initialized'},\n            $jobs,\n            $completion,\n            $options->{'guest-agent'},\n            $options->{bwlimit},\n            $source->{bitmap},\n        );\n    }\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Blockdev.pm",
    "content": "package PVE::QemuServer::Blockdev;\n\nuse strict;\nuse warnings;\n\nuse Digest::SHA;\nuse Fcntl qw(S_ISBLK S_ISCHR);\nuse File::stat;\nuse JSON;\n\nuse PVE::JSONSchema qw(json_bool);\nuse PVE::Storage;\n\nuse PVE::QemuServer::Drive qw(drive_is_cdrom);\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd qsd_qmp_peer vm_qmp_peer);\n\nuse base qw(Exporter);\n\nour @EXPORT_OK = qw(\n    generate_file_blockdev\n    generate_format_blockdev\n);\n\n# gives ($host, $port, $export)\nmy $NBD_TCP_PATH_RE_3 = qr/nbd:(\\S+):(\\d+):exportname=(\\S+)/;\nmy $NBD_UNIX_PATH_RE_2 = qr/nbd:unix:(\\S+):exportname=(\\S+)/;\n\nsub is_nbd {\n    my ($drive) = @_;\n\n    return 1 if $drive->{file} =~ $NBD_TCP_PATH_RE_3;\n    return 1 if $drive->{file} =~ $NBD_UNIX_PATH_RE_2;\n    return 0;\n}\n\nmy sub tpm_backup_node_name {\n    my ($type, $drive_id) = @_;\n\n    if ($type eq 'fmt') {\n        return \"drive-$drive_id-backup\"; # this is the top node\n    } elsif ($type eq 'file') {\n        return \"$drive_id-backup-file\"; # drop the \"drive-\" prefix to be sure, max length is 31\n    }\n\n    die \"unknown node type '$type' for TPM backup node\";\n}\n\nmy sub fleecing_node_name {\n    my ($type, $drive_id, $options) = @_;\n\n    $drive_id .= '-backup' if $options->{'tpm-backup'};\n\n    if ($type eq 'fmt') {\n        return \"drive-$drive_id-fleecing\"; # this is the top node for fleecing\n    } elsif ($type eq 'file') {\n        return \"$drive_id-fleecing-file\"; # drop the \"drive-\" prefix to be sure, max length is 31\n    }\n\n    die \"unknown node type '$type' for fleecing\";\n}\n\nmy sub is_fleecing_top_node {\n    my ($node_name) = @_;\n\n    return $node_name =~ m/-fleecing$/ ? 1 : 0;\n}\n\nsub qdev_id_to_drive_id {\n    my ($qdev_id) = @_;\n\n    if ($qdev_id =~ m|^/machine/peripheral/(virtio(\\d+))/virtio-backend$|) {\n        return $1;\n    } elsif ($qdev_id =~ m|^/machine/system\\.flash0$|) {\n        return 'pflash0';\n    } elsif ($qdev_id =~ m|^/machine/system\\.flash1$|) {\n        return 'efidisk0';\n    }\n\n    return $qdev_id; # for SCSI/SATA/IDE it's the same\n}\n\n=pod\n\n=head3 get_block_info\n\n    my $block_info = get_block_info($vmid);\n    my $inserted = $block_info->{$drive_key}->{inserted};\n    my $node_name = $inserted->{'node-name'};\n    my $block_node_size = $inserted->{image}->{'virtual-size'};\n\nReturns a hash reference with the information from the C<query-block> QMP command indexed by\nconfiguration drive keys like C<scsi2>. See the QMP documentation for details.\n\nParameters:\n\n=over\n\n=item C<$vmid>: The ID of the virtual machine to query.\n\n=back\n\n=cut\n\nsub get_block_info {\n    my ($vmid) = @_;\n\n    my $block_info = {};\n\n    my $qmp_block_info = mon_cmd($vmid, \"query-block\");\n    for my $info ($qmp_block_info->@*) {\n        my $qdev_id = $info->{qdev} or next;\n        my $drive_id = qdev_id_to_drive_id($qdev_id);\n        $block_info->{$drive_id} = $info;\n    }\n\n    return $block_info;\n}\n\nsub get_node_name {\n    my ($type, $drive_id, $volid, $options) = @_;\n\n    return fleecing_node_name($type, $drive_id, $options) if $options->{fleecing};\n    return tpm_backup_node_name($type, $drive_id) if $options->{'tpm-backup'};\n\n    my $snap = $options->{'snapshot-name'};\n\n    my $info = \"drive=$drive_id,\";\n    $info .= \"snap=$snap,\" if defined($snap);\n    $info .= \"volid=$volid\";\n\n    my $hash = substr(Digest::SHA::sha256_hex($info), 0, 30);\n\n    my $prefix = \"\";\n    if ($type eq 'alloc-track') {\n        $prefix = 'a';\n    } elsif ($type eq 'file') {\n        $prefix = 'e';\n    } elsif ($type eq 'fmt') {\n        $prefix = 'f';\n    } elsif ($type eq 'zeroinit') {\n        $prefix = 'z';\n    } else {\n        die \"unknown node type '$type'\";\n    }\n    # node-name must start with an alphabetical character\n    return \"${prefix}${hash}\";\n}\n\nsub parse_top_node_name {\n    my ($node_name) = @_;\n\n    if ($node_name =~ m/^drive-(.+)$/) {\n        my $drive_id = $1;\n        return $drive_id if PVE::QemuServer::Drive::is_valid_drivename($drive_id);\n    }\n\n    return;\n}\n\nsub top_node_name {\n    my ($drive_id) = @_;\n\n    return \"drive-$drive_id\";\n}\n\nsub get_node_name_below_throttle {\n    my ($qmp_peer, $device_id, $assert_top_is_throttle) = @_;\n\n    my $top;\n    if ($qmp_peer->{type} eq 'qmp') { # get_block_info() only works if there are front-end devices.\n        my $block_info = get_block_info($qmp_peer->{id});\n        my $drive_id = $device_id =~ s/^drive-//r;\n        $top = $block_info->{$drive_id}->{inserted};\n    } else {\n        my $named_block_node_info = qmp_cmd($qmp_peer, 'query-named-block-nodes');\n        for my $info ($named_block_node_info->@*) {\n            next if $info->{'node-name'} ne $device_id;\n            $top = $info;\n            last;\n        }\n    }\n    die \"no block node found for drive '$device_id'\\n\" if !$top;\n\n    if ($top->{drv} ne 'throttle') {\n        die \"$device_id: unexpected top node $top->{'node-name'} ($top->{drv})\\n\"\n            if $assert_top_is_throttle;\n        # before the switch to -blockdev, the top node was not throttle\n        return $top->{'node-name'};\n    }\n\n    my $children = { map { $_->{child} => $_ } $top->{children}->@* };\n\n    if (my $node_name = $children->{file}->{'node-name'}) {\n        return $node_name;\n    }\n\n    die \"$device_id: throttle node without file child node name!\\n\";\n}\n\nmy sub read_only_json_option {\n    my ($drive, $options) = @_;\n\n    return json_bool($drive->{ro} || drive_is_cdrom($drive) || $options->{'read-only'});\n}\n\n# Common blockdev options that need to be set across the whole throttle->fmt->file chain.\nmy sub add_common_options {\n    my ($blockdev, $drive, $options) = @_;\n\n    if (!drive_is_cdrom($drive)) {\n        $blockdev->{discard} = $drive->{discard} && $drive->{discard} eq 'on' ? 'unmap' : 'ignore';\n        $blockdev->{'detect-zeroes'} = PVE::QemuServer::Drive::detect_zeroes_cmdline_option($drive);\n    }\n\n    $blockdev->{'read-only'} = read_only_json_option($drive, $options);\n}\n\nmy sub throttle_group_id {\n    my ($drive_id) = @_;\n\n    return \"throttle-drive-$drive_id\";\n}\n\nsub generate_throttle_group {\n    my ($drive) = @_;\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n\n    my $limits = {};\n\n    for my $type (['', '-total'], [_rd => '-read'], [_wr => '-write']) {\n        my ($dir, $qmpname) = @$type;\n        if (my $v = $drive->{\"mbps$dir\"}) {\n            $limits->{\"bps$qmpname\"} = int($v * 1024 * 1024);\n        }\n        if (my $v = $drive->{\"mbps${dir}_max\"}) {\n            $limits->{\"bps$qmpname-max\"} = int($v * 1024 * 1024);\n        }\n        if (my $v = $drive->{\"bps${dir}_max_length\"}) {\n            $limits->{\"bps$qmpname-max-length\"} = int($v);\n        }\n        if (my $v = $drive->{\"iops${dir}\"}) {\n            $limits->{\"iops$qmpname\"} = int($v);\n        }\n        if (my $v = $drive->{\"iops${dir}_max\"}) {\n            $limits->{\"iops$qmpname-max\"} = int($v);\n        }\n        if (my $v = $drive->{\"iops${dir}_max_length\"}) {\n            $limits->{\"iops$qmpname-max-length\"} = int($v);\n        }\n    }\n\n    return {\n        id => throttle_group_id($drive_id),\n        limits => $limits,\n        'qom-type' => 'throttle-group',\n    };\n}\n\nmy sub generate_blockdev_drive_cache {\n    my ($drive, $scfg) = @_;\n\n    my $cache_direct = PVE::QemuServer::Drive::drive_uses_cache_direct($drive, $scfg);\n    return {\n        direct => json_bool($cache_direct),\n        'no-flush' => json_bool($drive->{cache} && $drive->{cache} eq 'unsafe'),\n    };\n}\n\nsub generate_file_blockdev {\n    my ($storecfg, $drive, $machine_version, $options) = @_;\n\n    my $blockdev = {};\n    my $scfg = undef;\n\n    delete $options->{'snapshot-name'}\n        if $options->{'snapshot-name'} && $options->{'snapshot-name'} eq 'current';\n\n    die \"generate_file_blockdev called without volid/path\\n\" if !$drive->{file};\n    die \"generate_file_blockdev called with 'none'\\n\" if $drive->{file} eq 'none';\n    # FIXME use overlay and new config option to define storage for temp write device\n    die \"'snapshot' option is not yet supported for '-blockdev'\\n\" if $drive->{snapshot};\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n\n    if ($drive->{file} =~ m/^$NBD_UNIX_PATH_RE_2$/) {\n        my $server = { type => 'unix', path => \"$1\" };\n        $blockdev = { driver => 'nbd', server => $server, export => \"$2\" };\n    } elsif ($drive->{file} =~ m/^$NBD_TCP_PATH_RE_3$/) {\n        my ($host, $port, $export) = ($1, $2, $3);\n        if ($host =~ m/^\\[(.*)\\]$/) { # IPv6 address needs to be passed without square brackets\n            $host = $1;\n        }\n        # port is also a string in QAPI\n        my $server = { type => 'inet', host => \"$host\", port => \"$port\" };\n        $blockdev = { driver => 'nbd', server => $server, export => \"$export\" };\n    } elsif ($drive->{file} eq 'cdrom') {\n        my $path = PVE::QemuServer::Drive::get_iso_path($storecfg, $drive->{file});\n        $blockdev = { driver => 'host_cdrom', filename => \"$path\" };\n    } elsif ($drive->{file} =~ m|^/|) {\n        my $path = $drive->{file};\n        # The 'file' driver only works for regular files. The check below is taken from\n        # block/file-posix.c:hdev_probe_device() in QEMU. To detect CD-ROM host devices, QEMU issues\n        # an ioctl, while the code here relies on the media=cdrom flag instead.\n        my $st = File::stat::stat($path) or die \"stat for '$path' failed - $!\\n\";\n        my $driver = 'file';\n        if (S_ISCHR($st->mode) || S_ISBLK($st->mode)) {\n            $driver = drive_is_cdrom($drive) ? 'host_cdrom' : 'host_device';\n        }\n        $blockdev = { driver => \"$driver\", filename => \"$path\" };\n    } else {\n        my $volid = $drive->{file};\n        my ($storeid) = PVE::Storage::parse_volume_id($volid);\n\n        my $vtype = (PVE::Storage::parse_volname($storecfg, $drive->{file}))[0];\n        die \"$drive_id: explicit media parameter is required for iso images\\n\"\n            if !defined($drive->{media}) && defined($vtype) && $vtype eq 'iso';\n\n        my $storage_opts = { hints => {} };\n        $storage_opts->{hints}->{'efi-disk'} = 1 if $drive->{interface} eq 'efidisk';\n        $storage_opts->{'snapshot-name'} = $options->{'snapshot-name'}\n            if defined($options->{'snapshot-name'});\n        $blockdev =\n            PVE::Storage::qemu_blockdev_options($storecfg, $volid, $machine_version, $storage_opts);\n        $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n    }\n\n    # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329\n    # It also needs the rbd_cache_policy set to 'writeback' on the RBD side, which is done by the\n    # storage layer.\n    if ($blockdev->{driver} eq 'rbd' && $drive->{interface} eq 'efidisk') {\n        $blockdev->{cache} = { direct => JSON::false, 'no-flush' => JSON::false };\n    } else {\n        $blockdev->{cache} = generate_blockdev_drive_cache($drive, $scfg);\n    }\n\n    my $driver = $blockdev->{driver};\n    # only certain drivers have the aio setting\n    if ($driver eq 'file' || $driver eq 'host_cdrom' || $driver eq 'host_device') {\n        $blockdev->{aio} =\n            PVE::QemuServer::Drive::aio_cmdline_option($scfg, $drive, $blockdev->{cache}->{direct});\n    }\n\n    $blockdev->{'node-name'} = get_node_name('file', $drive_id, $drive->{file}, $options);\n\n    add_common_options($blockdev, $drive, $options);\n\n    return $blockdev;\n}\n\nsub generate_format_blockdev {\n    my ($storecfg, $drive, $child, $options) = @_;\n\n    die \"generate_format_blockdev called without volid/path\\n\" if !$drive->{file};\n    die \"generate_format_blockdev called with 'none'\\n\" if $drive->{file} eq 'none';\n    die \"generate_format_blockdev called with NBD path\\n\" if is_nbd($drive);\n\n    delete($options->{'snapshot-name'})\n        if $options->{'snapshot-name'} && $options->{'snapshot-name'} eq 'current';\n\n    my $scfg;\n    my $format;\n    my $volid = $drive->{file};\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n    my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);\n\n    # For PVE-managed volumes, use the format from the storage layer and prevent overrides via the\n    # drive's 'format' option. For unmanaged volumes, fallback to 'raw' to avoid auto-detection by\n    # QEMU.\n    if ($storeid) {\n        $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n        $format = PVE::QemuServer::Drive::checked_volume_format($storecfg, $volid);\n        if ($drive->{format} && $drive->{format} ne $format) {\n            die \"drive '$drive->{interface}$drive->{index}' - volume '$volid'\"\n                . \" - 'format=$drive->{format}' option different from storage format '$format'\\n\";\n        }\n    } else {\n        $format = $drive->{format} // 'raw';\n    }\n\n    my $node_name = get_node_name('fmt', $drive_id, $drive->{file}, $options);\n\n    my $blockdev = {\n        'node-name' => \"$node_name\",\n        driver => \"$format\",\n        file => $child,\n        cache => $child->{cache}, # define cache option on both format && file node like libvirt\n    };\n\n    add_common_options($blockdev, $drive, $options);\n\n    if (defined($options->{size})) {\n        die \"blockdev: 'size' is only supported for 'raw' format\" if $format ne 'raw';\n        $blockdev->{size} = int($options->{size});\n    }\n\n    # see bug #6543: without this option, fragmentation can lead to the qcow2 file growing larger\n    # than what qemu-img measure reports, which is problematic for qcow2-on-top-of-LVM\n    # TODO test and consider enabling this in general\n    if ($scfg && $scfg->{'snapshot-as-volume-chain'}) {\n        $blockdev->{'discard-no-unref'} = JSON::true if $format eq 'qcow2';\n    }\n\n    return $blockdev;\n}\n\nmy sub generate_backing_blockdev {\n    use feature 'current_sub';\n    my ($storecfg, $snapshots, $deviceid, $drive, $machine_version, $options) = @_;\n\n    my $snap_id = $options->{'snapshot-name'};\n    my $snapshot = $snapshots->{$snap_id};\n    my $parentid = $snapshot->{parent};\n\n    my $volid = $drive->{file};\n\n    my $snap_file_blockdev = generate_file_blockdev($storecfg, $drive, $machine_version, $options);\n    $snap_file_blockdev->{filename} = $snapshot->{file};\n\n    my $snap_fmt_blockdev =\n        generate_format_blockdev($storecfg, $drive, $snap_file_blockdev, $options);\n\n    if ($parentid) {\n        my $options = { 'snapshot-name' => $parentid };\n        $snap_fmt_blockdev->{backing} = __SUB__->(\n            $storecfg, $snapshots, $deviceid, $drive, $machine_version, $options,\n        );\n    }\n    return $snap_fmt_blockdev;\n}\n\nmy sub generate_backing_chain_blockdev {\n    my ($storecfg, $deviceid, $drive, $machine_version) = @_;\n\n    my $volid = $drive->{file};\n\n    my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);\n    my $parentid = $snapshots->{'current'}->{parent};\n    return undef if !$parentid;\n    my $options = { 'snapshot-name' => $parentid };\n    return generate_backing_blockdev(\n        $storecfg, $snapshots, $deviceid, $drive, $machine_version, $options,\n    );\n}\n\nsub generate_throttle_blockdev {\n    my ($drive, $child, $options) = @_;\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n\n    my $blockdev = {\n        driver => \"throttle\",\n        'node-name' => top_node_name($drive_id),\n        'throttle-group' => throttle_group_id($drive_id),\n        file => $child,\n    };\n\n    add_common_options($blockdev, $drive, $options);\n\n    return $blockdev;\n}\n\nsub generate_drive_blockdev {\n    my ($storecfg, $drive, $machine_version, $options) = @_;\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n\n    die \"generate_drive_blockdev called without volid/path\\n\" if !$drive->{file};\n    die \"generate_drive_blockdev called with 'none'\\n\" if $drive->{file} eq 'none';\n\n    my $child = generate_file_blockdev($storecfg, $drive, $machine_version, $options);\n    if (!is_nbd($drive)) {\n        $child = generate_format_blockdev($storecfg, $drive, $child, $options);\n\n        my $support_qemu_snapshots =\n            PVE::Storage::volume_qemu_snapshot_method($storecfg, $drive->{file});\n        if ($support_qemu_snapshots && $support_qemu_snapshots eq 'mixed') {\n            my $backing_chain = generate_backing_chain_blockdev(\n                $storecfg, \"drive-$drive_id\", $drive, $machine_version,\n            );\n            $child->{backing} = $backing_chain if $backing_chain;\n        }\n    }\n\n    if ($options->{'zero-initialized'}) {\n        my $node_name = get_node_name('zeroinit', $drive_id, $drive->{file}, $options);\n        $child = { driver => 'zeroinit', file => $child, 'node-name' => \"$node_name\" };\n    }\n\n    if (my $live_restore = $options->{'live-restore'}) {\n        my $node_name = get_node_name('alloc-track', $drive_id, $drive->{file}, $options);\n        $child = {\n            driver => 'alloc-track',\n            'auto-remove' => JSON::true,\n            backing => $live_restore->{blockdev},\n            file => $child,\n            'node-name' => \"$node_name\",\n        };\n    }\n\n    if ($drive->{scsiblock}) {\n        # When using scsi-block for the front-end device, throttling would not work in any case, and\n        # the throttle block driver doesn't allow doing the necessary ioctls(), so don't attach a\n        # throttle filter. Implementing live mirroring for such disks would require special care!\n        $child->{'node-name'} = top_node_name($drive_id);\n        return $child;\n    }\n\n    # for fleecing and TPM backup, this is already the top node\n    return $child if $options->{fleecing} || $options->{'tpm-backup'} || $options->{'no-throttle'};\n\n    # this is the top filter entry point, use $drive-drive_id as nodename\n    return generate_throttle_blockdev($drive, $child, $options);\n}\n\nsub generate_pbs_blockdev {\n    my ($pbs_conf, $pbs_name) = @_;\n\n    my $blockdev = {\n        driver => 'pbs',\n        'node-name' => \"$pbs_name\",\n        'read-only' => JSON::true,\n        archive => \"$pbs_conf->{archive}\",\n        repository => \"$pbs_conf->{repository}\",\n        snapshot => \"$pbs_conf->{snapshot}\",\n    };\n    $blockdev->{namespace} = \"$pbs_conf->{namespace}\" if $pbs_conf->{namespace};\n    $blockdev->{keyfile} = \"$pbs_conf->{keyfile}\" if $pbs_conf->{keyfile};\n\n    return $blockdev;\n}\n\nmy sub blockdev_add {\n    my ($qmp_peer, $blockdev) = @_;\n\n    eval { qmp_cmd($qmp_peer, 'blockdev-add', $blockdev->%*); };\n    if (my $err = $@) {\n        my $node_name = $blockdev->{'node-name'} // 'undefined';\n        die \"adding blockdev '$node_name' failed : $err\\n\" if $@;\n    }\n\n    return;\n}\n\n=pod\n\n=head3 attach\n\n    my ($node_name, $read_only) = attach($storecfg, $id, $drive, $options);\n\nAttach the drive C<$drive> to the VM C<$id> considering the additional options C<$options>.\nReturns the node name of the (topmost) attached block device node and whether the node is read-only.\n\nParameters:\n\n=over\n\n=item C<$storecfg>: The storage configuration.\n\n=item C<$id>: The ID of the virtual machine or QEMU storage daemon.\n\n=item C<$drive>: The drive as parsed from a virtual machine configuration.\n\n=item C<$options>: A hash reference with additional options.\n\n=over\n\n=item C<< $options->{fleecing} >>: Generate and attach a block device for backup fleecing.\n\n=item C<< $options->{'no-throttle'} >>: Do not insert a throttle node as the top node.\n\n=item C<< $options->{'read-only'} >>: Attach the image as read-only irrespective of the\nconfiguration in C<$drive>.\n\n=item C<< $options->{size} >>: Attach the image with this virtual size. Must be smaller than the\nactual size of the image. The image format must be C<raw>.\n\n=item C<< $options->{'snapshot-name'} >>: Attach this snapshot of the volume C<< $drive->{file} >>,\nrather than the volume itself.\n\n=item C<< $options->{'tpm-backup'} >>: Generate and attach a block device for backing up the TPM\nstate image.\n\n=item C<< $options->{'qsd'} >>: Rather than attaching to a VM, attach to a QEMU storage daemon.\n\n=back\n\n=back\n\n=cut\n\nsub attach {\n    my ($storecfg, $id, $drive, $options) = @_;\n\n    my $qmp_peer = $options->{qsd} ? qsd_qmp_peer($id) : vm_qmp_peer($id);\n\n    my $machine_version;\n    if ($options->{qsd}) { # qemu-storage-daemon runs with the installed binary version\n        $machine_version =\n            'pc-i440fx-' . PVE::QemuServer::Machine::latest_installed_machine_version();\n    } else {\n        $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($id);\n    }\n\n    my $blockdev = generate_drive_blockdev($storecfg, $drive, $machine_version, $options);\n\n    my $throttle_group_id;\n    if (parse_top_node_name($blockdev->{'node-name'})) { # device top nodes need a throttle group\n        my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n        $throttle_group_id = throttle_group_id($drive_id);\n    }\n\n    eval {\n        if ($throttle_group_id) {\n            # Try to remove potential left-over.\n            qmp_cmd($qmp_peer, 'object-del', id => $throttle_group_id, noerr => 1);\n\n            my $throttle_group = generate_throttle_group($drive);\n            qmp_cmd($qmp_peer, 'object-add', $throttle_group->%*);\n        }\n\n        blockdev_add($qmp_peer, $blockdev);\n    };\n    if (my $err = $@) {\n        if ($throttle_group_id) {\n            eval { qmp_cmd($qmp_peer, 'object-del', id => $throttle_group_id); };\n        }\n        die $err;\n    }\n\n    return ($blockdev->{'node-name'}, $blockdev->{'read-only'});\n}\n\n=pod\n\n=head3 detach\n\n    detach($qmp_peer, $node_name);\n\nDetach the block device C<$node_name> from the QMP peer C<$qmp_peer>. Also removes associated child\nblock nodes.\n\nParameters:\n\n=over\n\n=item C<$qmp_peer>: QMP peer information.\n\n=item C<$node_name>: The node name identifying the block node in QEMU.\n\n=back\n\n=cut\n\nsub detach {\n    my ($qmp_peer, $node_name) = @_;\n\n    die \"Blockdev::detach - no node name\\n\" if !$node_name;\n\n    my $block_info = qmp_cmd($qmp_peer, \"query-named-block-nodes\");\n    $block_info = { map { $_->{'node-name'} => $_ } $block_info->@* };\n\n    my $remove_throttle_group_id;\n    if ((my $drive_id = parse_top_node_name($node_name)) && $block_info->{$node_name}) {\n        $remove_throttle_group_id = throttle_group_id($drive_id);\n    }\n\n    while ($node_name) {\n        last if !$block_info->{$node_name}; # already gone\n\n        my $res = qmp_cmd($qmp_peer, 'blockdev-del', 'node-name' => \"$node_name\", noerr => 1);\n        if (my $err = $res->{error}) {\n            last if $err =~ m/Failed to find node with node-name/; # already gone\n            die \"deleting blockdev '$node_name' failed : $err\\n\";\n        }\n\n        my $children = { map { $_->{child} => $_ } $block_info->{$node_name}->{children}->@* };\n        # Recursively remove 'file' child nodes. QEMU will auto-remove implicitly added child nodes,\n        # but e.g. the child of the top throttle node might have been explicitly added as a mirror\n        # target, and needs to be removed manually.\n        $node_name = $children->{file}->{'node-name'};\n    }\n\n    if ($remove_throttle_group_id) {\n        eval { qmp_cmd($qmp_peer, 'object-del', id => $remove_throttle_group_id); };\n        die \"removing throttle group failed - $@\\n\" if $@;\n    }\n\n    return;\n}\n\nsub detach_tpm_backup_node {\n    my ($vmid) = @_;\n\n    detach(vm_qmp_peer($vmid), \"drive-tpmstate0-backup\");\n}\n\nsub detach_fleecing_block_nodes {\n    my ($vmid, $log_func) = @_;\n\n    my $block_info = mon_cmd($vmid, \"query-named-block-nodes\");\n    for my $info ($block_info->@*) {\n        my $node_name = $info->{'node-name'};\n        next if !is_fleecing_top_node($node_name);\n\n        $log_func->('info', \"detaching (old) fleecing image '$node_name'\");\n        eval { detach(vm_qmp_peer($vmid), $node_name) };\n        $log_func->('warn', \"error detaching (old) fleecing image '$node_name' - $@\") if $@;\n    }\n}\n\nsub resize {\n    my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;\n\n    my $running = PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    PVE::Storage::volume_resize($storecfg, $volid, $size, $running);\n\n    return if !$running;\n\n    my $block_info = get_block_info($vmid);\n    my $drive_id = $deviceid =~ s/^drive-//r;\n    my $inserted = $block_info->{$drive_id}->{inserted}\n        or die \"no block node inserted for drive '$drive_id'\\n\";\n\n    my $padding = (1024 - $size % 1024) % 1024;\n    $size = $size + $padding;\n\n    mon_cmd(\n        $vmid,\n        \"block_resize\",\n        # Need to use the top throttle node, not the node below, because QEMU won't update the size\n        # of the top node otherwise, even though it's a filter node (as of QEMU 10.0). For legacy\n        # -drive, there is no top throttle node, so this also is the correct node.\n        'node-name' => \"$inserted->{'node-name'}\",\n        size => int($size),\n        timeout => 60,\n    );\n}\n\nmy sub blockdev_change_medium {\n    my ($storecfg, $vmid, $qdev_id, $drive) = @_;\n\n    # force eject if locked\n    mon_cmd($vmid, \"blockdev-open-tray\", force => JSON::true, id => \"$qdev_id\");\n    mon_cmd($vmid, \"blockdev-remove-medium\", id => \"$qdev_id\");\n    detach(vm_qmp_peer($vmid), \"drive-$qdev_id\");\n\n    return if $drive->{file} eq 'none';\n\n    attach($storecfg, $vmid, $drive, {});\n    mon_cmd($vmid, \"blockdev-insert-medium\", id => \"$qdev_id\", 'node-name' => \"drive-$qdev_id\");\n    mon_cmd($vmid, \"blockdev-close-tray\", id => \"$qdev_id\");\n}\n\nsub change_medium {\n    my ($storecfg, $vmid, $qdev_id, $drive) = @_;\n\n    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n    # for the switch to -blockdev\n    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {\n        blockdev_change_medium($storecfg, $vmid, $qdev_id, $drive);\n    } else {\n        # force eject if locked\n        mon_cmd($vmid, \"eject\", force => JSON::true, id => \"$qdev_id\");\n\n        my ($path, $format) = PVE::QemuServer::Drive::get_path_and_format($storecfg, $drive);\n\n        if ($path) { # no path for 'none'\n            mon_cmd(\n                $vmid, \"blockdev-change-medium\",\n                id => \"$qdev_id\",\n                filename => \"$path\",\n                format => \"$format\",\n            );\n        }\n    }\n}\n\nsub set_io_throttle {\n    my (\n        $vmid,\n        $deviceid,\n        $bps,\n        $bps_rd,\n        $bps_wr,\n        $iops,\n        $iops_rd,\n        $iops_wr,\n        $bps_max,\n        $bps_rd_max,\n        $bps_wr_max,\n        $iops_max,\n        $iops_rd_max,\n        $iops_wr_max,\n        $bps_max_length,\n        $bps_rd_max_length,\n        $bps_wr_max_length,\n        $iops_max_length,\n        $iops_rd_max_length,\n        $iops_wr_max_length,\n    ) = @_;\n\n    return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n    # for the switch to -blockdev\n    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {\n        mon_cmd(\n            $vmid,\n            'qom-set',\n            path => \"throttle-$deviceid\",\n            property => \"limits\",\n            value => {\n                'bps-total' => int($bps),\n                'bps-read' => int($bps_rd),\n                'bps-write' => int($bps_wr),\n                'iops-total' => int($iops),\n                'iops-read' => int($iops_rd),\n                'iops-write' => int($iops_wr),\n                'bps-total-max' => int($bps_max),\n                'bps-read-max' => int($bps_rd_max),\n                'bps-write-max' => int($bps_wr_max),\n                'iops-total-max' => int($iops_max),\n                'iops-read-max' => int($iops_rd_max),\n                'iops-write-max' => int($iops_wr_max),\n                'bps-total-max-length' => int($bps_max_length),\n                'bps-read-max-length' => int($bps_rd_max_length),\n                'bps-write-max-length' => int($bps_wr_max_length),\n                'iops-total-max-length' => int($iops_max_length),\n                'iops-read-max-length' => int($iops_rd_max_length),\n                'iops-write-max-length' => int($iops_wr_max_length),\n            },\n        );\n    } else {\n        mon_cmd(\n            $vmid, \"block_set_io_throttle\",\n            device => $deviceid,\n            bps => int($bps),\n            bps_rd => int($bps_rd),\n            bps_wr => int($bps_wr),\n            iops => int($iops),\n            iops_rd => int($iops_rd),\n            iops_wr => int($iops_wr),\n            bps_max => int($bps_max),\n            bps_rd_max => int($bps_rd_max),\n            bps_wr_max => int($bps_wr_max),\n            iops_max => int($iops_max),\n            iops_rd_max => int($iops_rd_max),\n            iops_wr_max => int($iops_wr_max),\n            bps_max_length => int($bps_max_length),\n            bps_rd_max_length => int($bps_rd_max_length),\n            bps_wr_max_length => int($bps_wr_max_length),\n            iops_max_length => int($iops_max_length),\n            iops_rd_max_length => int($iops_rd_max_length),\n            iops_wr_max_length => int($iops_wr_max_length),\n        );\n    }\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/CGroup.pm",
    "content": "package PVE::QemuServer::CGroup;\n\nuse strict;\nuse warnings;\n\nuse Net::DBus qw(dbus_uint64 dbus_boolean);\n\nuse PVE::Systemd;\nuse base('PVE::CGroup');\n\nsub get_subdir {\n    my ($self, $controller, $limiting) = @_;\n    my $vmid = $self->{vmid};\n    return \"qemu.slice/$vmid.scope/\";\n}\n\nsub scope {\n    my ($self) = @_;\n    return $self->{vmid} . '.scope';\n}\n\nmy sub set_unit_properties : prototype($$) {\n    my ($self, $properties) = @_;\n\n    PVE::Systemd::systemd_call(sub {\n        my ($if, $reactor, $finish_cb) = @_;\n        $if->SetUnitProperties($self->scope(), dbus_boolean(1), $properties);\n        return 1;\n    });\n}\n\n# Update the 'cpulimit' of VM.\n# Note that this is now the systemd API and we expect a value for `CPUQuota` as\n# set on VM startup, rather than cgroup values.\nsub change_cpu_quota {\n    my ($self, $quota, $period) = @_;\n\n    die \"period is not controlled for VMs\\n\" if defined($period);\n\n    $quota = dbus_uint64(defined($quota) ? ($quota * 10_000) : -1);\n    set_unit_properties($self, [[CPUQuotaPerSecUSec => $quota]]);\n\n    return;\n}\n\n# Update the 'cpuunits' of a VM.\n# Note that this is now the systemd API and we expect a value for `CPUQuota` as\n# set on VM startup, rather than cgroup values.\nsub change_cpu_shares {\n    my ($self, $shares) = @_;\n\n    $shares //= -1;\n\n    if (PVE::CGroup::cgroup_mode() == 2) {\n        set_unit_properties($self, [[CPUWeight => dbus_uint64($shares)]]);\n    } else {\n        set_unit_properties($self, [[CPUShares => dbus_uint64($shares)]]);\n    }\n\n    return;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/CPUConfig.pm",
    "content": "package PVE::QemuServer::CPUConfig;\n\nuse strict;\nuse warnings;\n\nuse JSON;\n\nuse PVE::JSONSchema qw(json_bool);\nuse PVE::Cluster qw(cfs_register_file cfs_read_file);\nuse PVE::ProcFSTools;\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Tools qw(run_command);\n\nuse PVE::QemuServer::Helpers qw(min_version get_host_arch);\n\nuse base qw(PVE::SectionConfig Exporter);\n\nour @EXPORT_OK = qw(\n    print_cpu_device\n    get_cpu_options\n    get_cpu_bitness\n    is_native_arch\n    get_amd_sev_object\n    get_intel_tdx_object\n    get_cvm_type\n);\n\nmy $arch_desc = {\n    description => \"Virtual processor architecture. Defaults to the host architecture.\",\n    type => 'string',\n    enum => [qw(x86_64 aarch64)],\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-cpu-arch\", $arch_desc);\n\n# under certain race-conditions, this module might be loaded before pve-cluster\n# has started completely, so ensure we don't prevent the FUSE mount with our dir\nif (PVE::Cluster::check_cfs_is_mounted(1)) {\n    mkdir \"/etc/pve/virtual-guest\";\n}\n\nmy $default_filename = \"virtual-guest/cpu-models.conf\";\ncfs_register_file(\n    $default_filename,\n    sub { PVE::QemuServer::CPUConfig->parse_config(@_); },\n    sub { PVE::QemuServer::CPUConfig->write_config(@_); },\n);\n\nsub load_custom_model_conf {\n    return cfs_read_file($default_filename);\n}\n\n#builtin models : reported-model is mandatory\nmy $builtin_models_by_arch = {\n    x86_64 => {\n        'x86-64-v2' => {\n            'reported-model' => 'qemu64',\n            flags => \"+popcnt;+pni;+sse4.1;+sse4.2;+ssse3\",\n        },\n        'x86-64-v2-AES' => {\n            'reported-model' => 'qemu64',\n            flags => \"+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3\",\n        },\n        'x86-64-v3' => {\n            'reported-model' => 'qemu64',\n            flags =>\n                \"+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave\",\n        },\n        'x86-64-v4' => {\n            'reported-model' => 'qemu64',\n            flags =>\n                \"+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave;+avx512f;+avx512bw;+avx512cd;+avx512dq;+avx512vl\",\n        },\n    },\n    aarch64 => {},\n};\n\nmy $all_builtin_models;\nfor my $arch (keys $builtin_models_by_arch->%*) {\n    for my $model (keys $builtin_models_by_arch->{$arch}->%*) {\n        $all_builtin_models->{$model} = $builtin_models_by_arch->{$arch}->{$model};\n    }\n}\n\nmy $depreacated_cpu_map = {\n    # there never was such a client CPU, so map it to the server one for backward compat\n    'Icelake-Client' => 'Icelake-Server',\n    'Icelake-Client-noTSX' => 'Icelake-Server-noTSX',\n};\n\nmy $cputypes_32bit = {\n    '486' => 1,\n    'pentium' => 1,\n    'pentium2' => 1,\n    'pentium3' => 1,\n    'coreduo' => 1,\n    'athlon' => 1,\n    'kvm32' => 1,\n    'qemu32' => 1,\n};\n\nmy $cpu_models_by_arch;\nmy $all_cpu_models;\n\n# helper to make it easier for testing\n# initializes both '$cpu_models_by_arch' and '$all_cpu_models'\nsub initialize_cpu_models {\n    $cpu_models_by_arch = {\n        x86_64 => {\n            # Intel CPUs\n            486 => 'GenuineIntel',\n            pentium => 'GenuineIntel',\n            pentium2 => 'GenuineIntel',\n            pentium3 => 'GenuineIntel',\n            coreduo => 'GenuineIntel',\n            core2duo => 'GenuineIntel',\n            Conroe => 'GenuineIntel',\n            Penryn => 'GenuineIntel',\n            Nehalem => 'GenuineIntel',\n            'Nehalem-IBRS' => 'GenuineIntel',\n            Westmere => 'GenuineIntel',\n            'Westmere-IBRS' => 'GenuineIntel',\n            SandyBridge => 'GenuineIntel',\n            'SandyBridge-IBRS' => 'GenuineIntel',\n            IvyBridge => 'GenuineIntel',\n            'IvyBridge-IBRS' => 'GenuineIntel',\n            Haswell => 'GenuineIntel',\n            'Haswell-IBRS' => 'GenuineIntel',\n            'Haswell-noTSX' => 'GenuineIntel',\n            'Haswell-noTSX-IBRS' => 'GenuineIntel',\n            Broadwell => 'GenuineIntel',\n            'Broadwell-IBRS' => 'GenuineIntel',\n            'Broadwell-noTSX' => 'GenuineIntel',\n            'Broadwell-noTSX-IBRS' => 'GenuineIntel',\n            'Skylake-Client' => 'GenuineIntel',\n            'Skylake-Client-IBRS' => 'GenuineIntel',\n            'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',\n            'Skylake-Client-v4' => 'GenuineIntel',\n            'Skylake-Server' => 'GenuineIntel',\n            'Skylake-Server-IBRS' => 'GenuineIntel',\n            'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',\n            'Skylake-Server-v4' => 'GenuineIntel',\n            'Skylake-Server-v5' => 'GenuineIntel',\n            'Cascadelake-Server' => 'GenuineIntel',\n            'Cascadelake-Server-v2' => 'GenuineIntel',\n            'Cascadelake-Server-noTSX' => 'GenuineIntel',\n            'Cascadelake-Server-v4' => 'GenuineIntel',\n            'Cascadelake-Server-v5' => 'GenuineIntel',\n            'Cooperlake' => 'GenuineIntel',\n            'Cooperlake-v2' => 'GenuineIntel',\n            KnightsMill => 'GenuineIntel',\n            'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1\n            'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1\n            'Icelake-Server' => 'GenuineIntel',\n            'Icelake-Server-noTSX' => 'GenuineIntel',\n            'Icelake-Server-v3' => 'GenuineIntel',\n            'Icelake-Server-v4' => 'GenuineIntel',\n            'Icelake-Server-v5' => 'GenuineIntel',\n            'Icelake-Server-v6' => 'GenuineIntel',\n            'Icelake-Server-v7' => 'GenuineIntel',\n            'SapphireRapids' => 'GenuineIntel',\n            'SapphireRapids-v2' => 'GenuineIntel',\n            'SapphireRapids-v3' => 'GenuineIntel',\n            'SapphireRapids-v4' => 'GenuineIntel',\n            'GraniteRapids' => 'GenuineIntel',\n            'GraniteRapids-v2' => 'GenuineIntel',\n            'GraniteRapids-v3' => 'GenuineIntel',\n            'SierraForest' => 'GenuineIntel',\n            'SierraForest-v2' => 'GenuineIntel',\n            'SierraForest-v3' => 'GenuineIntel',\n            'ClearwaterForest' => 'GenuineIntel',\n\n            # AMD CPUs\n            athlon => 'AuthenticAMD',\n            phenom => 'AuthenticAMD',\n            Opteron_G1 => 'AuthenticAMD',\n            Opteron_G2 => 'AuthenticAMD',\n            Opteron_G3 => 'AuthenticAMD',\n            Opteron_G4 => 'AuthenticAMD',\n            Opteron_G5 => 'AuthenticAMD',\n            EPYC => 'AuthenticAMD',\n            'EPYC-IBPB' => 'AuthenticAMD',\n            'EPYC-v3' => 'AuthenticAMD',\n            'EPYC-v4' => 'AuthenticAMD',\n            'EPYC-v5' => 'AuthenticAMD',\n            'EPYC-Rome' => 'AuthenticAMD',\n            'EPYC-Rome-v2' => 'AuthenticAMD',\n            'EPYC-Rome-v3' => 'AuthenticAMD',\n            'EPYC-Rome-v4' => 'AuthenticAMD',\n            'EPYC-Rome-v5' => 'AuthenticAMD',\n            'EPYC-Milan' => 'AuthenticAMD',\n            'EPYC-Milan-v2' => 'AuthenticAMD',\n            'EPYC-Milan-v3' => 'AuthenticAMD',\n            'EPYC-Genoa' => 'AuthenticAMD',\n            'EPYC-Genoa-v2' => 'AuthenticAMD',\n            'EPYC-Turin' => 'AuthenticAMD',\n\n            # generic types, use vendor from host node\n            kvm32 => 'default',\n            kvm64 => 'default',\n            qemu32 => 'default',\n            qemu64 => 'default',\n            max => 'default',\n        },\n        aarch64 => {\n            'a64fx' => 'ARM',\n            'cortex-a35' => 'ARM',\n            'cortex-a53' => 'ARM',\n            'cortex-a55' => 'ARM',\n            'cortex-a57' => 'ARM',\n            'cortex-a710' => 'ARM',\n            'cortex-a72' => 'ARM',\n            'cortex-a76' => 'ARM',\n            'neoverse-n1' => 'ARM',\n            'neoverse-n2' => 'ARM',\n            'neoverse-v1' => 'ARM',\n            # 32 bit and deprecated models were not added\n            max => 'default',\n        },\n    };\n\n    my $host_arch = get_host_arch();\n    # The host CPU model only exists if the arch matches\n    $cpu_models_by_arch->{$host_arch}->{host} = 'default';\n\n    $all_cpu_models = {};\n    for my $arch (keys $cpu_models_by_arch->%*) {\n        for my $model (keys $cpu_models_by_arch->{$arch}->%*) {\n            $all_cpu_models->{$model} = $cpu_models_by_arch->{$arch}->{$model};\n        }\n    }\n}\n\n=head3 get_cpu_models_by_arch\n\n    my $cpu_vendor = get_cpu_models_by_arch($arch)->{$cpu_model};\n\nReturns the a hash reference with all available CPU models for the given architecture C<$arch> as\nkeys. The associated value is the vendor of the CPU model. The parameter C<$arch> must be specified.\n\n=cut\n\nsub get_cpu_models_by_arch {\n    my ($arch) = @_;\n    die \"get_cpu_models_by_arch: required parameter 'arch' not specified\\n\" if !defined($arch);\n\n    initialize_cpu_models() if !defined($cpu_models_by_arch);\n    return $cpu_models_by_arch->{$arch};\n}\n\n=head3 get_all_cpu_models\n\n    my $cpu_vendor = get_all_cpu_models()->{$cpu_model};\n\nReturns a hash reference with all available CPU models as keys. The associated value is the vendor\nof the CPU model.\n\n=cut\n\nsub get_all_cpu_models {\n    initialize_cpu_models() if !defined($all_cpu_models);\n    return $all_cpu_models;\n}\n\nmy $supported_cpu_flags_by_arch = {\n    x86_64 => [\n        {\n            name => 'nested-virt',\n            description =>\n                \"Controls nested virtualization, namely 'svm' for AMD CPUs and 'vmx' for\"\n                . \" Intel CPUs. Live migration still only works if it's the same flag on both sides.\"\n                . \" Use a CPU model similar to the host, with the same vendor, not x86-64-vX!\",\n        },\n        {\n            name => 'md-clear',\n            description => \"Required to let the guest OS know if MDS is mitigated correctly.\",\n        },\n        {\n            name => 'pcid',\n            description =>\n                \"Meltdown fix cost reduction on Westmere, Sandy-, and IvyBridge Intel CPUs.\",\n        },\n        {\n            name => 'spec-ctrl',\n            description => \"Allows improved Spectre mitigation with Intel CPUs.\",\n        },\n        {\n            name => 'ssbd',\n            description => \"Protection for 'Speculative Store Bypass' for Intel models.\",\n        },\n        {\n            name => 'ibpb',\n            description => \"Allows improved Spectre mitigation with AMD CPUs.\",\n        },\n        {\n            name => 'virt-ssbd',\n            description => \"Basis for 'Speculative Store Bypass' protection for AMD models.\",\n        },\n        {\n            name => 'amd-ssbd',\n            description =>\n                \"Improves Spectre mitigation performance with AMD CPUs, best used with\"\n                . \" 'virt-ssbd'.\",\n        },\n        {\n            name => 'amd-no-ssb',\n            description =>\n                \"Notifies guest OS that host is not vulnerable for Spectre on AMD CPUs.\",\n        },\n        {\n            name => 'pdpe1gb',\n            description => \"Allow guest OS to use 1GB size pages, if host HW supports it.\",\n        },\n        {\n            name => 'hv-tlbflush',\n            description =>\n                \"Improve performance in overcommitted Windows guests. May lead to guest\"\n                . \" bluescreens on old CPUs.\",\n        },\n        {\n            name => 'hv-evmcs',\n            description =>\n                \"Improve performance for nested virtualization. Only supported on Intel\" . \" CPUs.\",\n        },\n        {\n            name => 'aes',\n            description => \"Activate AES instruction set for HW acceleration.\",\n        },\n    ],\n    aarch64 => [],\n};\n\nsub get_supported_cpu_flags {\n    my ($arch) = @_;\n    $arch = get_host_arch() if !defined($arch);\n    return $supported_cpu_flags_by_arch->{$arch};\n}\n\nmy $all_supported_cpu_flags = {};\nfor my $arch ($supported_cpu_flags_by_arch->%*) {\n    for my $flag ($supported_cpu_flags_by_arch->{$arch}->@*) {\n        $all_supported_cpu_flags->{ $flag->{name} } = 1;\n    }\n}\nmy @supported_cpu_flags_names = sort keys $all_supported_cpu_flags->%*;\nmy $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags_names)]})/;\nmy $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\\-_\\.]+)/;\n\nour $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\\w\\-\\._=]+,?)+)$/;\n\nmy $cpu_fmt = {\n    cputype => {\n        description =>\n            \"Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').\",\n        type => 'string',\n        format_description => 'string',\n        default => 'kvm64',\n        default_key => 1,\n        optional => 1,\n    },\n    'reported-model' => {\n        description =>\n            \"CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model.\"\n            . \" Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.\",\n        type => 'string',\n        enum => [sort { lc(\"$a\") cmp lc(\"$b\") } keys get_all_cpu_models()->%*],\n        default => 'kvm64',\n        optional => 1,\n    },\n    hidden => {\n        description =>\n            \"Do not identify as a KVM virtual machine. Only affects vCPUs with x86-64\"\n            . \" architecture.\",\n        type => 'boolean',\n        optional => 1,\n        default => 0,\n    },\n    'hv-vendor-id' => {\n        type => 'string',\n        pattern => qr/[a-zA-Z0-9]{1,12}/,\n        format_description => 'vendor-id',\n        description =>\n            'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',\n        optional => 1,\n    },\n    flags => {\n        description => \"List of additional CPU flags separated by ';'. Use '+FLAG' to enable,\"\n            . \" '-FLAG' to disable a flag. There is a special 'nested-virt' shorthand which\"\n            . \" controls nested virtualization for the current CPU ('svm' for AMD and 'vmx' for\"\n            . \" Intel). Custom CPU models can specify any flag supported by QEMU/KVM, VM-specific\"\n            . \" flags must be from the following set for security reasons: \"\n            . join(', ', @supported_cpu_flags_names),\n        format_description => '+FLAG[;-FLAG...]',\n        type => 'string',\n        pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,\n        optional => 1,\n    },\n    'guest-phys-bits' => {\n        type => 'integer',\n        minimum => 32, # see target/i386/cpu.c in QEMU\n        maximum => 64,\n        description => \"Number of physical address bits available to the guest.\",\n        optional => 1,\n    },\n    'phys-bits' => {\n        type => 'string',\n        format => 'pve-phys-bits',\n        format_description => '8-64|host',\n        description =>\n            \"The physical memory address bits that are reported to the guest OS. Should\"\n            . \" be smaller or equal to the host's. Set to 'host' to use value from host CPU, but\"\n            . \" note that doing so will break live migration to CPUs with other values.\",\n        optional => 1,\n    },\n};\n\nmy $sev_fmt = {\n    type => {\n        description => \"Enable standard SEV with type='std' or enable\"\n            . \" experimental SEV-ES with the 'es' option or enable\"\n            . \" experimental SEV-SNP with the 'snp' option.\",\n        type => 'string',\n        default_key => 1,\n        format_description => \"sev-type\",\n        enum => ['std', 'es', 'snp'],\n        maxLength => 3,\n    },\n    'no-debug' => {\n        description => \"Sets policy bit to disallow debugging of guest\",\n        type => 'boolean',\n        default => 0,\n        optional => 1,\n    },\n    'no-key-sharing' => {\n        description =>\n            \"Sets policy bit to disallow key sharing with other guests (Ignored for SEV-SNP)\",\n        type => 'boolean',\n        default => 0,\n        optional => 1,\n    },\n    'allow-smt' => {\n        description =>\n            \"Sets policy bit to allow Simultaneous Multi Threading (SMT) (Ignored unless for SEV-SNP)\",\n        type => 'boolean',\n        default => 1,\n        optional => 1,\n    },\n    \"kernel-hashes\" => {\n        description => \"Add kernel hashes to guest firmware for measured linux kernel launch\",\n        type => 'boolean',\n        default => 0,\n        optional => 1,\n    },\n};\nPVE::JSONSchema::register_format('pve-qemu-sev-fmt', $sev_fmt);\n\nmy $tdx_fmt = {\n    type => {\n        description => \"Enable TDX\",\n        type => 'string',\n        default_key => 1,\n        format_description => \"tdx-type\",\n        enum => ['tdx'],\n    },\n    'attestation' => {\n        description => \"Enable TDX attestation by including quote-generation-socket\",\n        type => 'boolean',\n        default => 1,\n    },\n    'vsock-cid' => {\n        type => 'integer',\n        minimum => 2,\n        default => 2,\n        optional => 1,\n        description => \"CID for vsock of Quote Generation Service\",\n    },\n    'vsock-port' => {\n        type => 'integer',\n        minimum => 0,\n        default => 4050,\n        optional => 1,\n        description => \"Port for vsock of Quote Generation Service\",\n    },\n};\nPVE::JSONSchema::register_format('pve-qemu-tdx-fmt', $tdx_fmt);\n\nPVE::JSONSchema::register_format('pve-phys-bits', \\&parse_phys_bits);\n\nsub parse_phys_bits {\n    my ($str, $noerr) = @_;\n\n    my $err_msg = \"value must be an integer between 8 and 64 or 'host'\\n\";\n\n    if ($str !~ m/^(host|\\d{1,2})$/) {\n        die $err_msg if !$noerr;\n        return;\n    }\n\n    if ($str =~ m/^\\d+$/ && (int($str) < 8 || int($str) > 64)) {\n        die $err_msg if !$noerr;\n        return;\n    }\n\n    return $str;\n}\n\n# $cpu_fmt describes both the CPU config passed as part of a VM config, as well\n# as the definition of a custom CPU model. There are some slight differences\n# though, which we catch in the custom validation functions below.\nPVE::JSONSchema::register_format('pve-cpu-conf', $cpu_fmt, \\&validate_cpu_conf);\n\nsub validate_cpu_conf {\n    my ($cpu) = @_;\n    # required, but can't be forced in schema since it's encoded in section header for custom models\n    die \"CPU is missing cputype\\n\" if !$cpu->{cputype};\n    return $cpu;\n}\nPVE::JSONSchema::register_format('pve-vm-cpu-conf', $cpu_fmt, \\&validate_vm_cpu_conf);\n\nsub validate_vm_cpu_conf {\n    my ($cpu) = @_;\n\n    validate_cpu_conf($cpu);\n\n    my $cputype = $cpu->{cputype};\n\n    # a VM-specific config is only valid if the cputype exists\n    if (is_custom_model($cputype)) {\n        # dies on unknown model\n        get_custom_model($cputype);\n    } elsif (\n        !defined(get_all_cpu_models()->{$cputype})\n        && !defined($all_builtin_models->{$cputype})\n    ) {\n        die \"Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\\n\";\n    }\n\n    # in a VM-specific config, certain properties are limited/forbidden\n\n    if ($cpu->{flags} && $cpu->{flags} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/) {\n        die \"VM-specific CPU flags must be a subset of: \"\n            . join(', ', @supported_cpu_flags_names) . \"\\n\";\n    }\n\n    if (defined($cpu->{'reported-model'})) {\n        die \"Property 'reported-model' not allowed in VM-specific CPU config.\\n\";\n    }\n\n    return $cpu;\n}\n\n# Section config settings\nmy $defaultData = {\n    # shallow copy, since SectionConfig modifies propertyList internally\n    propertyList => {%$cpu_fmt},\n};\n\nsub private {\n    return $defaultData;\n}\n\nsub options {\n    return {%$cpu_fmt};\n}\n\nsub type {\n    return 'cpu-model';\n}\n\nsub parse_section_header {\n    my ($class, $line) = @_;\n\n    my ($type, $sectionId, $errmsg, $config) = $class->SUPER::parse_section_header($line);\n\n    return if !$type;\n    return (\n        $type,\n        $sectionId,\n        $errmsg,\n        {\n            # name is given by section header, and we can always prepend 'custom-'\n            # since we're reading the custom CPU file\n            cputype => \"custom-$sectionId\",\n        },\n    );\n}\n\nsub write_config {\n    my ($class, $filename, $cfg) = @_;\n\n    mkdir \"/etc/pve/virtual-guest\";\n\n    for my $model (keys %{ $cfg->{ids} }) {\n        my $model_conf = $cfg->{ids}->{$model};\n\n        if (!is_custom_model($model_conf->{cputype})) {\n            die \"internal error: tried saving built-in CPU model (or missing prefix):\"\n                . \" $model_conf->{cputype}\\n\";\n        }\n\n        if (\"custom-$model\" ne $model_conf->{cputype}) {\n            die \"internal error: tried saving custom cpumodel with cputype (ignoring prefix:\"\n                . \" $model_conf->{cputype}) not equal to \\$cfg->ids entry ($model)\\n\";\n        }\n\n        # saved in section header\n        delete $model_conf->{cputype};\n    }\n\n    $class->SUPER::write_config($filename, $cfg);\n}\n\nsub add_cpu_json_properties {\n    my ($prop) = @_;\n\n    foreach my $opt (keys %$cpu_fmt) {\n        $prop->{$opt} = $cpu_fmt->{$opt};\n    }\n\n    return $prop;\n}\n\nsub get_cpu_models {\n    my ($include_custom, $arch) = @_;\n\n    $arch = get_host_arch() if !defined($arch);\n    my $cpu_vendor_list = get_cpu_models_by_arch($arch);\n\n    my $models = [];\n\n    for my $default_model (keys %{$cpu_vendor_list}) {\n        push @$models,\n            {\n                name => $default_model,\n                custom => 0,\n                vendor => $cpu_vendor_list->{$default_model},\n            };\n    }\n\n    my $builtin_models = $builtin_models_by_arch->{$arch};\n    for my $model (keys %{$builtin_models}) {\n        my $reported_model = $builtin_models->{$model}->{'reported-model'};\n        my $vendor = $cpu_vendor_list->{$reported_model};\n        push @$models,\n            {\n                name => $model,\n                custom => 0,\n                vendor => $vendor,\n            };\n    }\n\n    return $models if !$include_custom;\n\n    my $conf = load_custom_model_conf();\n    for my $custom_model (keys %{ $conf->{ids} }) {\n        my $reported_model = $conf->{ids}->{$custom_model}->{'reported-model'};\n        $reported_model //= $cpu_fmt->{'reported-model'}->{default};\n        my $vendor = get_all_cpu_models()->{$reported_model};\n        push @$models,\n            {\n                name => \"custom-$custom_model\",\n                custom => 1,\n                vendor => $vendor,\n            };\n    }\n\n    return $models;\n}\n\nsub is_custom_model {\n    my ($cputype) = @_;\n    return $cputype =~ m/^custom-/;\n}\n\n# Use this to get a single model in the format described by $cpu_fmt.\n# Allows names with and without custom- prefix.\nsub get_custom_model {\n    my ($name, $noerr) = @_;\n\n    $name =~ s/^custom-//;\n    my $conf = load_custom_model_conf();\n\n    my $entry = $conf->{ids}->{$name};\n    if (!defined($entry)) {\n        die \"Custom cputype '$name' not found\\n\" if !$noerr;\n        return;\n    }\n\n    my $model = {};\n    for my $property (keys %$cpu_fmt) {\n        if (my $value = $entry->{$property}) {\n            $model->{$property} = $value;\n        }\n    }\n\n    return $model;\n}\n\n# Print a QEMU device node for a given VM configuration for hotplugging CPUs\nsub print_cpu_device {\n    my ($conf, $arch, $id) = @_;\n\n    # FIXME: hot plugging other architectures like our unofficial aarch64 support?\n    die \"Hotplug of non x86_64 CPU not yet supported\" if $arch ne 'x86_64';\n\n    my $kvm = $conf->{kvm} // is_native_arch($arch);\n    my $cpu = get_default_cpu_type('x86_64', $kvm);\n    if (my $cputype = $conf->{cpu}) {\n        my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)\n            or die \"Cannot parse cpu description: $cputype\\n\";\n        $cpu = $cpuconf->{cputype};\n\n        my $builtin_models = $builtin_models_by_arch->{$arch};\n        if (my $model = $builtin_models->{$cpu}) {\n            $cpu = $model->{'reported-model'};\n        } elsif (is_custom_model($cputype)) {\n            my $custom_cpu = get_custom_model($cpu);\n\n            $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};\n        }\n        if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {\n            $cpu = $replacement_type;\n        }\n    }\n\n    my $cores = $conf->{cores} || 1;\n\n    my $current_core = ($id - 1) % $cores;\n    my $current_socket = int(($id - 1 - $current_core) / $cores);\n\n    return \"$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0\";\n}\n\n=head3 is_abstracted\n\n    if (is_abstracted($conf->{cpu})) {\n        # better check running CPU of QEMU instance\n    }\n\nCheck if the configured CPU is abstracted in any way, meaning that the actual CPU model cannot be\ndetermined from the configuration alone. Possible abstractions:\n\n=over\n\n=item custom model: can change with updates to the custom model configuration\n\n=item abstract flag: for example, nested-virt changes with the host\n\n=back\n\n=cut\n\nsub is_abstracted {\n    my ($cpu_property_string) = @_;\n\n    my $cpu_conf = PVE::JSONSchema::parse_property_string('pve-cpu-conf', $cpu_property_string);\n\n    return 1 if is_custom_model($cpu_conf->{cputype});\n\n    return if !$cpu_conf->{flags};\n    for my $flag (split(\";\", $cpu_conf->{flags})) {\n        if ($flag =~ $cpu_flag_supported_re) {\n            return 1 if $2 eq 'nested-virt';\n        }\n    }\n    return;\n}\n\n# Resolves multiple arrays of hashes representing CPU flags with metadata to a\n# single string in QEMU \"-cpu\" compatible format. Later arrays have higher\n# priority.\n#\n# Hashes take the following format:\n# {\n#     aes => {\n#         op => \"+\", # defaults to \"\" if undefined\n#         reason => \"to support AES acceleration\", # for override warnings\n#         value => \"\" # needed for kvm=off (value: off) etc...\n#     },\n#     ...\n# }\nmy sub resolve_cpu_flags {\n    my @flag_hashes = @_;\n\n    my $flags = {};\n\n    my $nested_flag;\n    my $nested_flag_resolved;\n    my $resolve_nested_flag = sub {\n        if (!$nested_flag_resolved) {\n            my $host_cpu_flags = PVE::ProcFSTools::read_cpuinfo()->{flags};\n            if ($host_cpu_flags =~ m/\\s(svm|vmx)\\s/) {\n                $nested_flag = $1;\n            } else {\n                log_warn(\n                    \"ignoring 'nested-virt' CPU flag - unable to resolve from host CPU flags\");\n            }\n            $nested_flag_resolved = 1;\n        }\n        return $nested_flag;\n    };\n\n    for my $hash (@flag_hashes) {\n        for my $flag_name (keys %$hash) {\n            if ($flag_name eq 'nested-virt') {\n                my $nested_flag_name = $resolve_nested_flag->() or next;\n                if ($hash->{$nested_flag_name}) {\n                    warn \"warning: CPU flag '$flag_name' overrides '$nested_flag_name'\\n\";\n                } else {\n                    print \"CPU flag '$flag_name' resolved to '$nested_flag_name'\\n\";\n                }\n                $hash->{$nested_flag_name} = delete($hash->{$flag_name});\n                $flag_name = $nested_flag_name;\n            }\n\n            my $flag = $hash->{$flag_name};\n            my $old_flag = $flags->{$flag_name};\n\n            $flag->{op} //= \"\";\n            $flag->{reason} //= \"unknown origin\";\n\n            if ($old_flag) {\n                my $value_changed = (defined($flag->{value}) != defined($old_flag->{value}))\n                    || (defined($flag->{value}) && $flag->{value} ne $old_flag->{value});\n\n                if ($old_flag->{op} eq $flag->{op} && !$value_changed) {\n                    $flags->{$flag_name}->{reason} .= \" & $flag->{reason}\";\n                    next;\n                }\n\n                my $old = print_cpuflag_hash($flag_name, $flags->{$flag_name});\n                my $new = print_cpuflag_hash($flag_name, $flag);\n                warn \"warning: CPU flag/setting $new overwrites $old\\n\";\n            }\n\n            $flags->{$flag_name} = $flag;\n        }\n    }\n\n    return $flags;\n}\n\nmy sub print_cpu_flags {\n    my ($flags) = @_;\n\n    my $flag_str = '';\n    # sort for command line stability\n    for my $flag_name (sort keys %$flags) {\n        $flag_str .= ',';\n        $flag_str .= $flags->{$flag_name}->{op};\n        $flag_str .= $flag_name;\n        $flag_str .= \"=$flags->{$flag_name}->{value}\"\n            if $flags->{$flag_name}->{value};\n    }\n\n    return $flag_str;\n}\n\nsub print_cpuflag_hash {\n    my ($flag_name, $flag) = @_;\n    my $formatted = \"'$flag->{op}$flag_name\";\n    $formatted .= \"=$flag->{value}\" if defined($flag->{value});\n    $formatted .= \"'\";\n    $formatted .= \" ($flag->{reason})\" if defined($flag->{reason});\n    return $formatted;\n}\n\nsub parse_cpuflag_list {\n    my ($re, $reason, $flaglist) = @_;\n\n    my $res = {};\n    return $res if !$flaglist;\n\n    foreach my $flag (split(\";\", $flaglist)) {\n        if ($flag =~ m/^$re$/) {\n            $res->{$2} = { op => $1, reason => $reason };\n        }\n    }\n\n    return $res;\n}\n\nmy sub check_phys_bits_above_40_compat {\n    my ($bios, $cpu_type, $cpu_flags) = @_;\n\n    # Would need to check CPU model expansion for others, but that information is not cheap to get\n    # right now. Checking with 'qemu64' and 'kvm64' should cover most problematic scenarios.\n    return if $cpu_type ne 'qemu64' && $cpu_type ne 'kvm64';\n\n    return if !$bios || $bios ne 'ovmf';\n\n    if (!$cpu_flags->{pdpe1gb} || $cpu_flags->{pdpe1gb}->{op} eq '-') {\n        log_warn(\"OVMF firmware might limit CPU 'phys-bits' to 40\"\n            . \" - enable the 'pdpe1gb' CPU flag to avoid this\");\n    }\n}\n\n# Calculate QEMU's '-cpu' argument from a given VM configuration\nsub get_cpu_options {\n    my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;\n\n    my $cputype = get_default_cpu_type($arch, $kvm);\n\n    my $cpu = {};\n    my $custom_cpu;\n    my $builtin_cpu;\n    my $hv_vendor_id;\n    if (my $cpu_prop_str = $conf->{cpu}) {\n        $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)\n            or die \"Cannot parse cpu description: $cpu_prop_str\\n\";\n\n        $cputype = $cpu->{cputype};\n        my $builtin_models = $builtin_models_by_arch->{$arch};\n        if (my $model = $builtin_models->{$cputype}) {\n            $cputype = $model->{'reported-model'};\n            $builtin_cpu->{flags} = $model->{'flags'};\n        } elsif (is_custom_model($cputype)) {\n            $custom_cpu = get_custom_model($cputype);\n\n            $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};\n            $kvm_off = $custom_cpu->{hidden} if defined($custom_cpu->{hidden});\n            $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};\n        }\n\n        if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {\n            $cputype = $replacement_type;\n        }\n\n        # VM-specific settings override custom CPU config\n        $kvm_off = $cpu->{hidden} if defined($cpu->{hidden});\n        $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});\n    }\n\n    die \"CPU model '$cputype' does not exist for configured vCPU architecture '$arch'\\n\"\n        if !defined(get_cpu_models_by_arch($arch)->{$cputype});\n\n    my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch, $machine_version);\n\n    my $hv_flags;\n    if ($kvm && $arch eq 'x86_64') {\n        $hv_flags = get_hyperv_enlightenments(\n            $winversion,\n            $machine_version,\n            $conf->{bios},\n            $gpu_passthrough,\n            $hv_vendor_id,\n        );\n    }\n\n    my $builtin_cputype_flags =\n        parse_cpuflag_list($cpu_flag_any_re, \"set by builtin CPU model\", $builtin_cpu->{flags});\n\n    my $custom_cputype_flags =\n        parse_cpuflag_list($cpu_flag_any_re, \"set by custom CPU model\", $custom_cpu->{flags});\n\n    my $vm_flags = parse_cpuflag_list($cpu_flag_supported_re, \"manually set for VM\", $cpu->{flags});\n\n    my $pve_forced_flags = {};\n    if ($cputype ne 'host' && $kvm && $arch eq 'x86_64') {\n        $pve_forced_flags->{'enforce'} = {\n            reason => \"error if requested CPU settings not available\",\n        };\n    }\n    if ($kvm_off && $arch eq 'x86_64') {\n        $pve_forced_flags->{'kvm'} = {\n            value => \"off\",\n            reason => \"hide KVM virtualization from guest\",\n        };\n    }\n\n    # For aarch64, QEMU does not have a vendor property for the -cpu commandline.\n    if ($arch eq 'x86_64') {\n        # $cputype is the \"reported-model\" for custom types, so we can just look up\n        # the vendor in the default list\n        my $cpu_vendor = get_cpu_models_by_arch($arch)->{$cputype} or die \"internal error\";\n        $pve_forced_flags->{'vendor'} = { value => $cpu_vendor } if $cpu_vendor ne 'default';\n    }\n\n    my $cpu_str = $cputype;\n\n    # will be resolved in parameter order\n    my $resolved_flags = resolve_cpu_flags(\n        $pve_flags,\n        $hv_flags,\n        $builtin_cputype_flags,\n        $custom_cputype_flags,\n        $vm_flags,\n        $pve_forced_flags,\n    );\n    $cpu_str .= print_cpu_flags($resolved_flags);\n\n    my $using_phys_bits_above_40;\n\n    for my $phys_bits_opt (qw(guest-phys-bits phys-bits)) {\n        my $phys_bits = '';\n        for my $cpu_conf ($custom_cpu, $cpu) {\n            next if !defined($cpu_conf);\n            my $conf_val = $cpu_conf->{$phys_bits_opt};\n            next if !$conf_val;\n            if ($conf_val eq 'host') {\n                die \"unexpected value 'host' for guest-phys-bits\"\n                    if $phys_bits_opt eq 'guest-phys-bits';\n\n                $phys_bits = \",host-phys-bits=true\";\n\n                my $host_phys_bits = PVE::QemuServer::Helpers::get_host_phys_address_bits();\n                $using_phys_bits_above_40 = 1 if defined($host_phys_bits) && $host_phys_bits > 40;\n            } else {\n                $phys_bits = \",${phys_bits_opt}=${conf_val}\";\n\n                $using_phys_bits_above_40 = 1 if $phys_bits_opt eq 'phys-bits' && $conf_val > 40;\n            }\n        }\n        $cpu_str .= $phys_bits;\n    }\n\n    check_phys_bits_above_40_compat($conf->{bios}, $cputype, $resolved_flags)\n        if $using_phys_bits_above_40;\n\n    return ('-cpu', $cpu_str);\n}\n\n# Some hardcoded flags required by certain configurations\nsub get_pve_cpu_flags {\n    my ($conf, $kvm, $cputype, $arch, $machine_version) = @_;\n\n    my $pve_flags = {};\n    my $pve_msg = \"set by PVE;\";\n\n    if ($cputype eq 'kvm64' && $arch eq 'x86_64') {\n        $pve_flags->{'lahf_lm'} = {\n            op => '+',\n            reason => \"$pve_msg to support Windows 8.1+\",\n        };\n    }\n\n    if ($conf->{ostype} && $conf->{ostype} eq 'solaris') {\n        $pve_flags->{'x2apic'} = {\n            op => '-',\n            reason => \"$pve_msg incompatible with Solaris\",\n        };\n    }\n\n    if ($cputype eq 'kvm64' || $cputype eq 'kvm32') {\n        $pve_flags->{'sep'} = {\n            op => '+',\n            reason => \"$pve_msg to support Windows 8+ and improve Windows XP+\",\n        };\n    }\n\n    if ($cputype =~ m/^Opteron/) {\n        $pve_flags->{'rdtscp'} = {\n            op => '-',\n            reason => \"$pve_msg broken on AMD Opteron\",\n        };\n    }\n\n    if (min_version($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') {\n        $pve_flags->{'kvm_pv_unhalt'} = {\n            op => '+',\n            reason => \"$pve_msg to improve Linux guest spinlock performance\",\n        };\n        $pve_flags->{'kvm_pv_eoi'} = {\n            op => '+',\n            reason => \"$pve_msg to improve Linux guest interrupt performance\",\n        };\n    }\n\n    return $pve_flags;\n}\n\nsub get_hyperv_enlightenments {\n    my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;\n\n    return if $winversion < 6;\n    return if $bios && $bios eq 'ovmf' && $winversion < 8;\n\n    my $flags = {};\n    my $default_reason = \"automatic Hyper-V enlightenment for Windows\";\n    my $flagfn = sub {\n        my ($flag, $value, $reason) = @_;\n        $flags->{$flag} = {\n            reason => $reason // $default_reason,\n            value => $value,\n        };\n    };\n\n    my $hv_vendor_set = defined($hv_vendor_id);\n    if ($gpu_passthrough || $hv_vendor_set) {\n        $hv_vendor_id //= 'proxmox';\n        $flagfn->(\n            'hv_vendor_id',\n            $hv_vendor_id,\n            $hv_vendor_set\n            ? \"custom hv_vendor_id set\"\n            : \"NVIDIA workaround for GPU passthrough\",\n        );\n    }\n\n    if (min_version($machine_version, 2, 3)) {\n        $flagfn->('hv_spinlocks', '0x1fff');\n        $flagfn->('hv_vapic');\n        $flagfn->('hv_time');\n    } else {\n        $flagfn->('hv_spinlocks', '0xffff');\n    }\n\n    if (min_version($machine_version, 2, 6)) {\n        $flagfn->('hv_reset');\n        $flagfn->('hv_vpindex');\n        $flagfn->('hv_runtime');\n    }\n\n    if ($winversion >= 7) {\n        my $win7_reason = $default_reason . \" 7 and higher\";\n        $flagfn->('hv_relaxed', undef, $win7_reason);\n\n        if (min_version($machine_version, 2, 12)) {\n            $flagfn->('hv_synic', undef, $win7_reason);\n            $flagfn->('hv_stimer', undef, $win7_reason);\n        }\n\n        if (min_version($machine_version, 3, 1)) {\n            $flagfn->('hv_ipi', undef, $win7_reason);\n        }\n    }\n\n    return $flags;\n}\n\nsub get_cpu_from_running_vm {\n    my ($pid) = @_;\n\n    my $cmdline = PVE::QemuServer::Helpers::parse_cmdline($pid);\n    die \"could not read commandline of running machine\\n\"\n        if !$cmdline->{cpu}->{value};\n\n    # sanitize and untaint value\n    $cmdline->{cpu}->{value} =~ $qemu_cmdline_cpu_re;\n    return $1;\n}\n\nsub get_default_cpu_type {\n    my ($arch, $kvm) = @_;\n\n    my $cputype = $kvm ? 'kvm64' : 'qemu64';\n    $cputype = 'cortex-a57' if $arch eq 'aarch64';\n\n    return $cputype;\n}\n\nsub is_native_arch($) {\n    my ($arch) = @_;\n    return get_host_arch() eq $arch;\n}\n\nsub get_cpu_bitness {\n    my ($cpu_prop_str, $arch) = @_;\n\n    $arch //= get_host_arch();\n\n    my $cputype = get_default_cpu_type($arch, 0);\n\n    if ($cpu_prop_str) {\n        my $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)\n            or die \"Cannot parse cpu description: $cpu_prop_str\\n\";\n\n        $cputype = $cpu->{cputype};\n\n        my $builtin_models = $builtin_models_by_arch->{$arch};\n        if (my $model = $builtin_models->{$cputype}) {\n            $cputype = $model->{'reported-model'};\n        } elsif (is_custom_model($cputype)) {\n            my $custom_cpu = get_custom_model($cputype);\n            $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};\n        }\n    }\n\n    return $cputypes_32bit->{$cputype} ? 32 : 64 if $arch eq 'x86_64';\n    return 64 if $arch eq 'aarch64';\n\n    die \"unsupported architecture '$arch'\\n\";\n}\n\nsub get_hw_capabilities {\n    # Get reduced-phys-bits & cbitpos from host-hw-capabilities.json\n    # TODO: Find better location than /run/qemu-server/\n    my $filename = '/run/qemu-server/host-hw-capabilities.json';\n    if (!-e $filename) {\n        die \"$filename does not exist. Please check the status of query-machine-capabilities: \"\n            . \"systemctl status query-machine-capabilities\\n\";\n    }\n    my $json_text = PVE::Tools::file_get_contents($filename);\n    ($json_text) = $json_text =~ /(.*)/; # untaint json text\n    my $hw_capabilities = eval { decode_json($json_text) };\n    if (my $err = $@) {\n        die $err;\n    }\n    return $hw_capabilities;\n}\n\nsub get_cvm_type {\n    my ($conf) = @_;\n\n    if ($conf->{'amd-sev'}) {\n        my $sev = PVE::JSONSchema::parse_property_string($sev_fmt, $conf->{'amd-sev'});\n        return $sev->{type};\n    } elsif ($conf->{'intel-tdx'}) {\n        my $tdx = PVE::JSONSchema::parse_property_string($tdx_fmt, $conf->{'intel-tdx'});\n        return $tdx->{type};\n    } else {\n        return undef;\n    }\n}\n\nsub get_amd_sev_object {\n    my ($amd_sev, $bios) = @_;\n\n    my $amd_sev_conf = PVE::JSONSchema::parse_property_string($sev_fmt, $amd_sev);\n    my $sev_hw_caps = get_hw_capabilities()->{'amd-sev'};\n\n    if (!$sev_hw_caps->{'sev-support'}) {\n        die \"Your CPU does not support AMD SEV.\\n\";\n    }\n    if ($amd_sev_conf->{type} eq 'es' && !$sev_hw_caps->{'sev-support-es'}) {\n        die \"Your CPU does not support AMD SEV-ES.\\n\";\n    }\n    if ($amd_sev_conf->{type} eq 'snp' && !$sev_hw_caps->{'sev-support-snp'}) {\n        die \"Your CPU does not support AMD SEV-SNP.\\n\";\n    }\n    if (!$bios || $bios ne 'ovmf') {\n        die \"To use AMD SEV, you need to change the BIOS to OVMF.\\n\";\n    }\n\n    my $sev_mem_object = '';\n    my $policy;\n    if ($amd_sev_conf->{type} eq 'es' || $amd_sev_conf->{type} eq 'std') {\n        $sev_mem_object .= 'sev-guest,id=sev0';\n        $sev_mem_object .= ',cbitpos=' . $sev_hw_caps->{cbitpos};\n        $sev_mem_object .= ',reduced-phys-bits=' . $sev_hw_caps->{'reduced-phys-bits'};\n\n        # guest policy bit calculation as described here:\n        # https://documentation.suse.com/sles/15-SP5/html/SLES-amd-sev/article-amd-sev.html#table-guestpolicy\n        $policy = 0;\n        $policy |= 1 << 0 if $amd_sev_conf->{'no-debug'};\n        $policy |= 1 << 1 if $amd_sev_conf->{'no-key-sharing'};\n        $policy |= 1 << 2 if $amd_sev_conf->{type} eq 'es';\n        # disable migration with bit 3 nosend to prevent amd-sev-migration-attack\n        $policy |= 1 << 3;\n    } elsif ($amd_sev_conf->{type} eq 'snp') {\n        $sev_mem_object .= 'sev-snp-guest,id=sev0';\n        $sev_mem_object .= ',cbitpos=' . $sev_hw_caps->{cbitpos};\n        $sev_mem_object .= ',reduced-phys-bits=' . $sev_hw_caps->{'reduced-phys-bits'};\n\n        # guest policy bit calculation as described in chapter 4.3:\n        # https://www.amd.com/system/files/TechDocs/56860.pdf\n        # Reserved bit must be one\n        $policy = 1 << 17;\n        $policy |= 1 << 16\n            if !defined($amd_sev_conf->{'allow-smt'}) || $amd_sev_conf->{'allow-smt'};\n        $policy |= 1 << 19 if !$amd_sev_conf->{'no-debug'};\n    }\n\n    $sev_mem_object .= ',policy=' . sprintf(\"%#x\", $policy);\n    $sev_mem_object .= ',kernel-hashes=on' if ($amd_sev_conf->{'kernel-hashes'});\n    return $sev_mem_object;\n}\n\nsub get_quote_generation_socket {\n    my ($conf) = @_;\n\n    my $socket = { type => 'vsock' };\n\n    die \"Missing cid for vsock.\\n\" if !defined($conf->{'vsock-cid'});\n    die \"Missing port for vsock.\\n\" if !defined($conf->{'vsock-port'});\n\n    # Both are strings in the QMP schema\n    $socket->{'cid'} = \"$conf->{'vsock-cid'}\";\n    $socket->{'port'} = \"$conf->{'vsock-port'}\";\n\n    return $socket;\n}\n\nsub get_intel_tdx_object {\n    my ($intel_tdx, $bios) = @_;\n    my $intel_tdx_conf = PVE::JSONSchema::parse_property_string($tdx_fmt, $intel_tdx);\n    my $tdx_hw_caps = get_hw_capabilities()->{'intel-tdx'};\n\n    if (!$tdx_hw_caps->{'tdx-support'}) {\n        die \"Your CPU does not support Intel TDX.\\n\";\n    }\n    if (!$bios || $bios ne 'ovmf') {\n        die \"To use Intel TDX, you need to change the BIOS to OVMF.\\n\";\n    }\n\n    my $tdx_object = {\n        'qom-type' => 'tdx-guest',\n        id => 'tdx0',\n    };\n\n    $tdx_object->{'quote-generation-socket'} = get_quote_generation_socket($intel_tdx_conf)\n        if $intel_tdx_conf->{'attestation'};\n\n    return $tdx_object;\n}\n\n__PACKAGE__->register();\n__PACKAGE__->init();\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Cfg2Cmd/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES=Timer.pm\n\n.PHONY: install\ninstall: $(SOURCES)\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/QemuServer/Cfg2Cmd/$$i; done\n"
  },
  {
    "path": "src/PVE/QemuServer/Cfg2Cmd/Timer.pm",
    "content": "package PVE::QemuServer::Cfg2Cmd::Timer;\n\nuse warnings;\nuse strict;\n\nsub generate {\n    my ($cfg2cmd) = @_;\n\n    my $time_drift_fix = $cfg2cmd->get_prop('tdf', 1);\n    my $acpi = $cfg2cmd->get_prop('acpi');\n    my $localtime = $cfg2cmd->get_prop('localtime', 1);\n    my $startdate = $cfg2cmd->get_prop('startdate');\n\n    if ($cfg2cmd->windows_version() >= 5) { # windows\n        $localtime = 1 if !defined($localtime);\n\n        # use time drift fix when acpi is enabled, but prefer explicitly set value\n        $time_drift_fix = 1 if $acpi && !defined($time_drift_fix);\n    }\n\n    if ($cfg2cmd->windows_version() >= 6) {\n        $cfg2cmd->add_global_flag('kvm-pit.lost_tick_policy=discard');\n        $cfg2cmd->add_machine_flag_if_supported('hpet', 'off');\n    } elsif ($cfg2cmd->is_linux() && $cfg2cmd->version_guard(10, 1, 0)) {\n        $cfg2cmd->add_machine_flag_if_supported('hpet', 'off');\n    }\n\n    $cfg2cmd->add_rtc_flag('driftfix=slew') if $time_drift_fix;\n\n    if ($startdate ne 'now') {\n        $cfg2cmd->add_rtc_flag(\"base=$startdate\");\n    } elsif ($localtime) {\n        $cfg2cmd->add_rtc_flag('base=localtime');\n    }\n\n    return;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Cfg2Cmd.pm",
    "content": "package PVE::QemuServer::Cfg2Cmd;\n\nuse warnings;\nuse strict;\n\nuse PVE::QemuServer::Cfg2Cmd::Timer;\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Machine;\n\nsub new {\n    my ($class, $conf, $defaults, $version_guard, $opts) = @_;\n\n    my $self = bless {\n        conf => $conf,\n        defaults => $defaults,\n        'version-guard' => $version_guard,\n    }, $class;\n\n    $self->{ostype} = $self->get_prop('ostype');\n    $self->{'windows-version'} = PVE::QemuServer::Helpers::windows_version($self->{ostype});\n\n    $self->{'machine-type'} =\n        PVE::QemuServer::Machine::get_vm_machine($conf, $opts->{forcemachine});\n\n    return $self;\n}\n\n=head3 get_prop\n\n    my $value = $self->get_prop($prop);\n\nReturn the configured value for the property C<$prop>. If no fallback to the default value should be\nmade, use C<$only_explicit>. Note that any such usage is likely an indication that the default value\nis not actually a static default, but that the default depends on context.\n\n=cut\n\nsub get_prop {\n    my ($self, $prop, $only_explicit) = @_;\n\n    my ($conf, $defaults) = $self->@{qw(conf defaults)};\n    return $conf->{$prop} if $only_explicit;\n    return defined($conf->{$prop}) ? $conf->{$prop} : $defaults->{$prop};\n}\n\nsub add_global_flag {\n    my ($self, $flag) = @_;\n\n    push $self->{'global-flags'}->@*, $flag;\n}\n\nsub global_flags {\n    my ($self) = @_;\n\n    return $self->{'global-flags'};\n}\n\n=head3 add_machine_flag_if_supported\n\n    my $success = $self->add_machine_flag_if_supported($flag_name, $value);\n\nAdd flag C<$flag_name> with value C<$value> to the machine flags if the current machine type\nsupports it. Returns whether the flag was added or not.\n\n=cut\n\nsub add_machine_flag_if_supported {\n    my ($self, $flag_name, $value) = @_;\n\n    return if !PVE::QemuServer::Machine::machine_supports_flag($self->{'machine-type'}, $flag_name);\n\n    push $self->{'machine-flags'}->@*, \"${flag_name}=${value}\";\n\n    return 1;\n}\n\nsub machine_flags {\n    my ($self) = @_;\n\n    return $self->{'machine-flags'};\n}\n\nsub add_rtc_flag {\n    my ($self, $flag) = @_;\n\n    push $self->{'rtc-flags'}->@*, $flag;\n}\n\nsub rtc_flags {\n    my ($self) = @_;\n\n    return $self->{'rtc-flags'};\n}\n\n=head3 is_linux\n\n    if ($self->is_linux()) {\n        do_something_for_linux_vms();\n    }\n\nCheck if the virtual machine is configured for running Linux. Does not include the C<l24> os type\nby default. Specify C<$include_l24> if that is desired.\n\n=cut\n\nsub is_linux {\n    my ($self, $include_l24) = @_;\n\n    return $self->{ostype} eq 'l26' || ($include_l24 && $self->{ostype} eq 'l24');\n}\n\nsub windows_version {\n    my ($self) = @_;\n\n    return $self->{'windows-version'};\n}\n\nsub version_guard {\n    my ($self, $major, $minor, $pve) = @_;\n\n    $self->{'version-guard'}->($major, $minor, $pve);\n}\n\nsub generate {\n    my ($self) = @_;\n\n    PVE::QemuServer::Cfg2Cmd::Timer::generate($self);\n\n    return $self;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Cloudinit.pm",
    "content": "package PVE::QemuServer::Cloudinit;\n\nuse strict;\nuse warnings;\n\nuse File::Path;\nuse Digest::SHA;\nuse URI::Escape;\nuse MIME::Base64 qw(encode_base64);\nuse Storable qw(dclone);\nuse JSON;\n\nuse PVE::Tools qw(run_command file_set_contents);\nuse PVE::Storage;\nuse PVE::QemuServer::Drive qw(checked_volume_format);\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Network;\n\nuse constant CLOUDINIT_DISK_SIZE => 4 * 1024 * 1024; # 4MiB in bytes\n\nsub commit_cloudinit_disk {\n    my ($conf, $vmid, $drive, $volname, $storeid, $files, $label) = @_;\n\n    my $path = \"/run/pve/cloudinit/$vmid/\";\n    mkpath $path;\n    foreach my $filepath (keys %$files) {\n        if ($filepath !~ m@^(.*)\\/[^/]+$@) {\n            die \"internal error: bad file name in cloud-init image: $filepath\\n\";\n        }\n        my $dirname = $1;\n        mkpath \"$path/$dirname\";\n\n        my $contents = $files->{$filepath};\n        file_set_contents(\"$path/$filepath\", $contents);\n    }\n\n    my $storecfg = PVE::Storage::config();\n    my $iso_path = PVE::Storage::path($storecfg, $drive->{file});\n    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n    my $format = checked_volume_format($storecfg, $drive->{file});\n\n    my $size = eval { PVE::Storage::volume_size_info($storecfg, $drive->{file}) };\n    if (!defined($size) || $size <= 0) {\n        $volname =~ m/(vm-$vmid-cloudinit(.\\Q$format\\E)?)/;\n        my $name = $1;\n        $size = 4 * 1024;\n        PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, $name, $size);\n        $size *= 1024; # vdisk alloc takes KB, qemu-img dd's osize takes byte\n    }\n    my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});\n    $plugin->activate_volume($storeid, $scfg, $volname);\n\n    print \"generating cloud-init ISO\\n\";\n    eval {\n        run_command([\n            ['genisoimage', '-quiet', '-iso-level', '3', '-J', '-R', '-V', $label, $path],\n            [\n                'qemu-img',\n                'dd',\n                '-n',\n                '-f',\n                'raw',\n                '-O',\n                $format,\n                'isize=0',\n                \"osize=$size\",\n                \"of=$iso_path\",\n            ],\n        ]);\n    };\n    my $err = $@;\n    rmtree($path);\n    die $err if $err;\n}\n\nsub get_cloudinit_format {\n    my ($conf) = @_;\n    if (defined(my $format = $conf->{citype})) {\n        return $format;\n    }\n\n    # No format specified, default based on ostype because windows'\n    # cloudbased-init only supports configdrivev2, whereas on linux we need\n    # to use mac addresses because regular cloudinit doesn't map 'ethX' to\n    # the new predictable network device naming scheme.\n    if (defined(my $ostype = $conf->{ostype})) {\n        return 'configdrive2'\n            if PVE::QemuServer::Helpers::windows_version($ostype);\n    }\n\n    return 'nocloud';\n}\n\nsub get_hostname_fqdn {\n    my ($conf, $vmid) = @_;\n    my $hostname = $conf->{name} // \"VM$vmid\";\n    my $fqdn;\n    if ($hostname =~ /\\./) {\n        $fqdn = $hostname;\n        $hostname =~ s/\\..*$//;\n    } elsif (my $search = $conf->{searchdomain}) {\n        $fqdn = \"$hostname.$search\";\n    } else {\n        $fqdn = $hostname;\n    }\n    return ($hostname, $fqdn);\n}\n\nsub get_dns_conf {\n    my ($conf) = @_;\n\n    # Same logic as in pve-container, but without the testcase special case\n    my $host_resolv_conf = PVE::INotify::read_file('resolvconf');\n\n    my $searchdomains = [split(/\\s+/, $conf->{searchdomain} // $host_resolv_conf->{search} // '')];\n\n    my $nameserver = $conf->{nameserver};\n    if (!defined($nameserver)) {\n        $nameserver = [grep { $_ } $host_resolv_conf->@{qw(dns1 dns2 dns3)}];\n    } else {\n        $nameserver = [split(/\\s+/, $nameserver)];\n    }\n\n    return ($searchdomains, $nameserver);\n}\n\nsub cloudinit_userdata {\n    my ($conf, $vmid, $mask_password) = @_;\n\n    my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);\n\n    my $content = \"#cloud-config\\n\";\n\n    $content .= \"hostname: $hostname\\n\";\n    $content .= \"manage_etc_hosts: true\\n\";\n    $content .= \"fqdn: $fqdn\\n\";\n\n    my $username = $conf->{ciuser};\n    my $password = $conf->{cipassword};\n\n    $content .= \"user: $username\\n\" if defined($username);\n    $content .= \"disable_root: False\\n\" if defined($username) && $username eq 'root';\n    if (defined($password)) {\n        if ($mask_password) {\n            $content .= \"password: **********\\n\";\n        } else {\n            $content .= \"password: $password\\n\";\n        }\n    }\n\n    if (defined(my $keys = $conf->{sshkeys})) {\n        $keys = URI::Escape::uri_unescape($keys);\n        $keys = [map { my $key = $_; chomp $key; $key } split(/\\n/, $keys)];\n        $keys = [grep { /\\S/ } @$keys];\n        $content .= \"ssh_authorized_keys:\\n\";\n        foreach my $k (@$keys) {\n            $content .= \"  - $k\\n\";\n        }\n    }\n    $content .= \"chpasswd:\\n\";\n    $content .= \"  expire: False\\n\";\n\n    if (!defined($username) || $username ne 'root') {\n        $content .= \"users:\\n\";\n        $content .= \"  - default\\n\";\n    }\n\n    $content .= \"package_upgrade: true\\n\" if !defined($conf->{ciupgrade}) || $conf->{ciupgrade};\n\n    return $content;\n}\n\nsub split_ip4 {\n    my ($ip) = @_;\n    my ($addr, $mask) = split('/', $ip);\n    die \"not a CIDR: $ip\\n\" if !defined $mask;\n    return ($addr, $PVE::Network::ipv4_reverse_mask->[$mask]);\n}\n\nsub configdrive2_network {\n    my ($conf) = @_;\n\n    my $content = \"auto lo\\n\";\n    $content .= \"iface lo inet loopback\\n\\n\";\n\n    my ($searchdomains, $nameservers) = get_dns_conf($conf);\n    if ($nameservers && @$nameservers) {\n        $nameservers = join(' ', @$nameservers);\n        $content .= \"        dns_nameservers $nameservers\\n\";\n    }\n    if ($searchdomains && @$searchdomains) {\n        $searchdomains = join(' ', @$searchdomains);\n        $content .= \"        dns_search $searchdomains\\n\";\n    }\n\n    my @ifaces = grep { /^net(\\d+)$/ } keys %$conf;\n    foreach my $iface (sort @ifaces) {\n        (my $id = $iface) =~ s/^net//;\n        next if !$conf->{\"ipconfig$id\"};\n        my $net = PVE::QemuServer::Network::parse_ipconfig($conf->{\"ipconfig$id\"});\n        $id = \"eth$id\";\n\n        $content .= \"auto $id\\n\";\n        if ($net->{ip}) {\n            if ($net->{ip} eq 'dhcp') {\n                $content .= \"iface $id inet dhcp\\n\";\n            } else {\n                my ($addr, $mask) = split_ip4($net->{ip});\n                $content .= \"iface $id inet static\\n\";\n                $content .= \"        address $addr\\n\";\n                $content .= \"        netmask $mask\\n\";\n                $content .= \"        gateway $net->{gw}\\n\" if $net->{gw};\n            }\n        }\n        if ($net->{ip6}) {\n            if ($net->{ip6} =~ /^(auto|dhcp)$/) {\n                $content .= \"iface $id inet6 $1\\n\";\n            } else {\n                my ($addr, $mask) = split('/', $net->{ip6});\n                $content .= \"iface $id inet6 static\\n\";\n                $content .= \"        address $addr\\n\";\n                $content .= \"        netmask $mask\\n\";\n                $content .= \"        gateway $net->{gw6}\\n\" if $net->{gw6};\n            }\n        }\n    }\n\n    return $content;\n}\n\nsub configdrive2_gen_metadata {\n    my ($user, $network) = @_;\n\n    my $uuid_str = Digest::SHA::sha1_hex($user . $network);\n    return configdrive2_metadata($uuid_str);\n}\n\nsub configdrive2_metadata {\n    my ($uuid) = @_;\n    return <<\"EOF\";\n{\n     \"uuid\": \"$uuid\",\n     \"network_config\": { \"content_path\": \"/content/0000\" }\n}\nEOF\n}\n\nsub generate_configdrive2 {\n    my ($conf, $vmid, $drive, $volname, $storeid) = @_;\n\n    my ($user_data, $network_data, $meta_data, $vendor_data) = get_custom_cloudinit_files($conf);\n    if (PVE::QemuServer::Helpers::windows_version($conf->{ostype})) {\n        $user_data = cloudinit_userdata($conf, $vmid) if !defined($user_data);\n        $network_data = cloudbase_network_eni($conf) if !defined($network_data);\n        $vendor_data = '' if !defined($vendor_data);\n\n        if (!defined($meta_data)) {\n            my $instance_id = cloudbase_gen_instance_id($user_data, $network_data);\n            $meta_data = cloudbase_configdrive2_metadata($instance_id, $conf);\n        }\n    } else {\n        $user_data = cloudinit_userdata($conf, $vmid) if !defined($user_data);\n        $network_data = configdrive2_network($conf) if !defined($network_data);\n        $vendor_data = '' if !defined($vendor_data);\n\n        if (!defined($meta_data)) {\n            $meta_data = configdrive2_gen_metadata($user_data, $network_data);\n        }\n    }\n\n    # we always allocate a 4MiB disk for cloudinit and with the overhead of the ISO\n    # make sure we always stay below it by keeping the sum of all files below 3 MiB\n    my $sum =\n        length($user_data) + length($network_data) + length($meta_data) + length($vendor_data);\n    die \"Cloud-Init sum of snippets too big (> 3 MiB)\\n\" if $sum > (3 * 1024 * 1024);\n\n    my $files = {\n        '/openstack/latest/user_data' => $user_data,\n        '/openstack/content/0000' => $network_data,\n        '/openstack/latest/meta_data.json' => $meta_data,\n        '/openstack/latest/vendor_data.json' => $vendor_data,\n    };\n    commit_cloudinit_disk($conf, $vmid, $drive, $volname, $storeid, $files, 'config-2');\n}\n\nsub cloudbase_network_eni {\n    my ($conf) = @_;\n\n    my $content = \"\";\n\n    my ($searchdomains, $nameservers) = get_dns_conf($conf);\n    if ($nameservers && @$nameservers) {\n        $nameservers = join(' ', @$nameservers);\n    }\n\n    my @ifaces = grep { /^net(\\d+)$/ } keys %$conf;\n    foreach my $iface (sort @ifaces) {\n        (my $id = $iface) =~ s/^net//;\n        next if !$conf->{\"ipconfig$id\"};\n        my $net = PVE::QemuServer::Network::parse_ipconfig($conf->{\"ipconfig$id\"});\n        $id = \"eth$id\";\n\n        $content .= \"auto $id\\n\";\n        if ($net->{ip}) {\n            if ($net->{ip} eq 'dhcp') {\n                $content .= \"iface $id inet dhcp\\n\";\n            } else {\n                my ($addr, $mask) = split_ip4($net->{ip});\n                $content .= \"iface $id inet static\\n\";\n                $content .= \"        address $addr\\n\";\n                $content .= \"        netmask $mask\\n\";\n                $content .= \"        gateway $net->{gw}\\n\" if $net->{gw};\n                $content .= \"        dns-nameservers $nameservers\\n\" if $nameservers;\n            }\n        }\n        if ($net->{ip6}) {\n            if ($net->{ip6} =~ /^(auto|dhcp)$/) {\n                $content .= \"iface $id inet6 $1\\n\";\n            } else {\n                my ($addr, $mask) = split('/', $net->{ip6});\n                $content .= \"iface $id inet6 static\\n\";\n                $content .= \"        address $addr\\n\";\n                $content .= \"        netmask $mask\\n\";\n                $content .= \"        gateway $net->{gw6}\\n\" if $net->{gw6};\n                $content .= \"        dns-nameservers $nameservers\\n\" if $nameservers;\n            }\n        }\n    }\n\n    return $content;\n}\n\nsub cloudbase_configdrive2_metadata {\n    my ($uuid, $conf) = @_;\n    my $meta_data = {\n        uuid => $uuid,\n        'network_config' => {\n            'content_path' => '/content/0000',\n        },\n    };\n    $meta_data->{'admin_pass'} = $conf->{cipassword} if $conf->{cipassword};\n    if (defined(my $keys = $conf->{sshkeys})) {\n        $keys = URI::Escape::uri_unescape($keys);\n        $keys = [map { my $key = $_; chomp $key; $key } split(/\\n/, $keys)];\n        $keys = [grep { /\\S/ } @$keys];\n        my $i = 0;\n        foreach my $k (@$keys) {\n            $meta_data->{'public_keys'}->{\"key-$i\"} = $k;\n            $i++;\n        }\n    }\n    my $json = encode_json($meta_data);\n    return $json;\n}\n\nsub cloudbase_gen_instance_id {\n    my ($user, $network) = @_;\n\n    my $uuid_str = Digest::SHA::sha1_hex($user . $network);\n    return $uuid_str;\n}\n\nsub generate_opennebula {\n    my ($conf, $vmid, $drive, $volname, $storeid) = @_;\n\n    my $content = \"\";\n\n    my $username = $conf->{ciuser} || \"root\";\n    $content .= \"USERNAME=$username\\n\" if defined($username);\n\n    if (defined(my $password = $conf->{cipassword})) {\n        $content .= \"CRYPTED_PASSWORD_BASE64=\" . encode_base64($password) . \"\\n\";\n    }\n\n    if (defined($conf->{sshkeys})) {\n        my $keys = [split(/\\s*\\n\\s*/, URI::Escape::uri_unescape($conf->{sshkeys}))];\n        $content .= \"SSH_PUBLIC_KEY=\\\"\" . join(\"\\n\", $keys->@*) . \"\\\"\\n\";\n    }\n\n    my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);\n    $content .= \"SET_HOSTNAME=$hostname\\n\";\n\n    my ($searchdomains, $nameservers) = get_dns_conf($conf);\n    $content .= 'DNS=\"' . join(' ', @$nameservers) . \"\\\"\\n\" if $nameservers && @$nameservers;\n    $content .= 'SEARCH_DOMAIN=\"' . join(' ', @$searchdomains) . \"\\\"\\n\"\n        if $searchdomains && @$searchdomains;\n\n    my $networkenabled = undef;\n    my @ifaces = grep { /^net(\\d+)$/ } keys %$conf;\n    foreach my $iface (sort @ifaces) {\n        (my $id = $iface) =~ s/^net//;\n        my $net = PVE::QemuServer::Network::parse_net($conf->{$iface});\n        next if !$conf->{\"ipconfig$id\"};\n        my $ipconfig = PVE::QemuServer::Network::parse_ipconfig($conf->{\"ipconfig$id\"});\n        my $ethid = \"ETH$id\";\n\n        my $mac = lc $net->{hwaddr};\n\n        if ($ipconfig->{ip}) {\n            $networkenabled = 1;\n\n            if ($ipconfig->{ip} eq 'dhcp') {\n                $content .= \"${ethid}_DHCP=YES\\n\";\n            } else {\n                my ($addr, $mask) = split_ip4($ipconfig->{ip});\n                $content .= \"${ethid}_IP=$addr\\n\";\n                $content .= \"${ethid}_MASK=$mask\\n\";\n                $content .= \"${ethid}_MAC=$mac\\n\";\n                $content .= \"${ethid}_GATEWAY=$ipconfig->{gw}\\n\" if $ipconfig->{gw};\n            }\n            $content .= \"${ethid}_MTU=$net->{mtu}\\n\" if $net->{mtu};\n        }\n\n        if ($ipconfig->{ip6}) {\n            $networkenabled = 1;\n            if ($ipconfig->{ip6} eq 'dhcp') {\n                $content .= \"${ethid}_DHCP6=YES\\n\";\n            } elsif ($ipconfig->{ip6} eq 'auto') {\n                $content .= \"${ethid}_AUTO6=YES\\n\";\n            } else {\n                my ($addr, $mask) = split('/', $ipconfig->{ip6});\n                $content .= \"${ethid}_IP6=$addr\\n\";\n                $content .= \"${ethid}_MASK6=$mask\\n\";\n                $content .= \"${ethid}_MAC6=$mac\\n\";\n                $content .= \"${ethid}_GATEWAY6=$ipconfig->{gw6}\\n\" if $ipconfig->{gw6};\n            }\n            $content .= \"${ethid}_MTU=$net->{mtu}\\n\" if $net->{mtu};\n        }\n    }\n\n    $content .= \"NETWORK=YES\\n\" if $networkenabled;\n\n    my $files = { '/context.sh' => $content };\n    commit_cloudinit_disk($conf, $vmid, $drive, $volname, $storeid, $files, 'CONTEXT');\n}\n\nsub nocloud_network_v2 {\n    my ($conf) = @_;\n\n    my $content = '';\n\n    my $head = \"version: 2\\n\" . \"ethernets:\\n\";\n\n    my $dns_done;\n\n    my @ifaces = grep { /^net(\\d+)$/ } keys %$conf;\n    foreach my $iface (sort @ifaces) {\n        (my $id = $iface) =~ s/^net//;\n        next if !$conf->{\"ipconfig$id\"};\n\n        # indentation - network interfaces are inside an 'ethernets' hash\n        my $i = '    ';\n\n        my $net = PVE::QemuServer::Network::parse_net($conf->{$iface});\n        my $ipconfig = PVE::QemuServer::Network::parse_ipconfig($conf->{\"ipconfig$id\"});\n\n        my $mac = $net->{macaddr}\n            or die \"network interface '$iface' has no mac address\\n\";\n\n        $content .= \"${i}$iface:\\n\";\n        $i .= '    ';\n        $content .= \"${i}match:\\n\" . \"${i}    macaddress: \\\"$mac\\\"\\n\" . \"${i}set-name: eth$id\\n\";\n        my @addresses;\n        if (defined(my $ip = $ipconfig->{ip})) {\n            if ($ip eq 'dhcp') {\n                $content .= \"${i}dhcp4: true\\n\";\n            } else {\n                push @addresses, $ip;\n            }\n        }\n        if (defined(my $ip = $ipconfig->{ip6})) {\n            if ($ip eq 'dhcp') {\n                $content .= \"${i}dhcp6: true\\n\";\n            } else {\n                push @addresses, $ip;\n            }\n        }\n        if (@addresses) {\n            $content .= \"${i}addresses:\\n\";\n            $content .= \"${i}- '$_'\\n\" foreach @addresses;\n        }\n        if (defined(my $gw = $ipconfig->{gw})) {\n            $content .= \"${i}gateway4: '$gw'\\n\";\n        }\n        if (defined(my $gw = $ipconfig->{gw6})) {\n            $content .= \"${i}gateway6: '$gw'\\n\";\n        }\n\n        next if $dns_done;\n        $dns_done = 1;\n\n        my ($searchdomains, $nameservers) = get_dns_conf($conf);\n        if ($searchdomains || $nameservers) {\n            $content .= \"${i}nameservers:\\n\";\n            if (defined($nameservers) && @$nameservers) {\n                $content .= \"${i}  addresses:\\n\";\n                $content .= \"${i}  - '$_'\\n\" foreach @$nameservers;\n            }\n            if (defined($searchdomains) && @$searchdomains) {\n                $content .= \"${i}  search:\\n\";\n                $content .= \"${i}  - '$_'\\n\" foreach @$searchdomains;\n            }\n        }\n    }\n\n    return $head . $content;\n}\n\nsub nocloud_network {\n    my ($conf) = @_;\n\n    my $content = \"version: 1\\n\" . \"config:\\n\";\n\n    my @ifaces = grep { /^net(\\d+)$/ } keys %$conf;\n    foreach my $iface (sort @ifaces) {\n        (my $id = $iface) =~ s/^net//;\n        next if !$conf->{\"ipconfig$id\"};\n\n        # indentation - network interfaces are inside an 'ethernets' hash\n        my $i = '    ';\n\n        my $net = PVE::QemuServer::Network::parse_net($conf->{$iface});\n        my $ipconfig = PVE::QemuServer::Network::parse_ipconfig($conf->{\"ipconfig$id\"});\n\n        my $mac = lc($net->{macaddr})\n            or die \"network interface '$iface' has no mac address\\n\";\n\n        $content .=\n            \"${i}- type: physical\\n\"\n            . \"${i}  name: eth$id\\n\"\n            . \"${i}  mac_address: '$mac'\\n\"\n            . \"${i}  subnets:\\n\";\n        $i .= '  ';\n        if (defined(my $ip = $ipconfig->{ip})) {\n            if ($ip eq 'dhcp') {\n                $content .= \"${i}- type: dhcp4\\n\";\n            } else {\n                my ($addr, $mask) = split_ip4($ip);\n                $content .=\n                    \"${i}- type: static\\n\"\n                    . \"${i}  address: '$addr'\\n\"\n                    . \"${i}  netmask: '$mask'\\n\";\n                if (defined(my $gw = $ipconfig->{gw})) {\n                    $content .= \"${i}  gateway: '$gw'\\n\";\n                }\n            }\n        }\n        if (defined(my $ip = $ipconfig->{ip6})) {\n            if ($ip eq 'dhcp') {\n                $content .= \"${i}- type: dhcp6\\n\";\n            } elsif ($ip eq 'auto') {\n                # SLAAC is only supported by cloud-init since 19.4\n                $content .= \"${i}- type: ipv6_slaac\\n\";\n            } else {\n                $content .= \"${i}- type: static6\\n\" . \"${i}  address: '$ip'\\n\";\n                if (defined(my $gw = $ipconfig->{gw6})) {\n                    $content .= \"${i}  gateway: '$gw'\\n\";\n                }\n            }\n        }\n    }\n\n    my $i = '    ';\n    my ($searchdomains, $nameservers) = get_dns_conf($conf);\n    if ($searchdomains || $nameservers) {\n        $content .= \"${i}- type: nameserver\\n\";\n        if (defined($nameservers) && @$nameservers) {\n            $content .= \"${i}  address:\\n\";\n            $content .= \"${i}  - '$_'\\n\" foreach @$nameservers;\n        }\n        if (defined($searchdomains) && @$searchdomains) {\n            $content .= \"${i}  search:\\n\";\n            $content .= \"${i}  - '$_'\\n\" foreach @$searchdomains;\n        }\n    }\n\n    return $content;\n}\n\nsub nocloud_metadata {\n    my ($uuid) = @_;\n    return \"instance-id: $uuid\\n\";\n}\n\nsub nocloud_gen_metadata {\n    my ($user, $network) = @_;\n\n    my $uuid_str = Digest::SHA::sha1_hex($user . $network);\n    return nocloud_metadata($uuid_str);\n}\n\nsub generate_nocloud {\n    my ($conf, $vmid, $drive, $volname, $storeid) = @_;\n\n    my ($user_data, $network_data, $meta_data, $vendor_data) = get_custom_cloudinit_files($conf);\n    $user_data = cloudinit_userdata($conf, $vmid) if !defined($user_data);\n    $network_data = nocloud_network($conf) if !defined($network_data);\n    $vendor_data = '' if !defined($vendor_data);\n\n    if (!defined($meta_data)) {\n        $meta_data = nocloud_gen_metadata($user_data, $network_data);\n    }\n\n    # we always allocate a 4MiB disk for cloudinit and with the overhead of the ISO\n    # make sure we always stay below it by keeping the sum of all files below 3 MiB\n    my $sum =\n        length($user_data) + length($network_data) + length($meta_data) + length($vendor_data);\n    die \"Cloud-Init sum of snippets too big (> 3 MiB)\\n\" if $sum > (3 * 1024 * 1024);\n\n    my $files = {\n        '/user-data' => $user_data,\n        '/network-config' => $network_data,\n        '/meta-data' => $meta_data,\n        '/vendor-data' => $vendor_data,\n    };\n    commit_cloudinit_disk($conf, $vmid, $drive, $volname, $storeid, $files, 'cidata');\n}\n\nsub get_custom_cloudinit_files {\n    my ($conf) = @_;\n\n    my $cicustom = $conf->{cicustom};\n    my $files =\n        $cicustom ? PVE::JSONSchema::parse_property_string('pve-qm-cicustom', $cicustom) : {};\n\n    my $network_volid = $files->{network};\n    my $user_volid = $files->{user};\n    my $meta_volid = $files->{meta};\n    my $vendor_volid = $files->{vendor};\n\n    my $storage_conf = PVE::Storage::config();\n\n    my $network_data;\n    if ($network_volid) {\n        $network_data = read_cloudinit_snippets_file($storage_conf, $network_volid);\n    }\n\n    my $user_data;\n    if ($user_volid) {\n        $user_data = read_cloudinit_snippets_file($storage_conf, $user_volid);\n    }\n\n    my $meta_data;\n    if ($meta_volid) {\n        $meta_data = read_cloudinit_snippets_file($storage_conf, $meta_volid);\n    }\n\n    my $vendor_data;\n    if ($vendor_volid) {\n        $vendor_data = read_cloudinit_snippets_file($storage_conf, $vendor_volid);\n    }\n\n    return ($user_data, $network_data, $meta_data, $vendor_data);\n}\n\nsub read_cloudinit_snippets_file {\n    my ($storage_conf, $volid) = @_;\n\n    my ($vtype, undef) = PVE::Storage::parse_volname($storage_conf, $volid);\n\n    die \"$volid is not in the snippets directory\\n\" if $vtype ne 'snippets';\n\n    my $full_path = PVE::Storage::abs_filesystem_path($storage_conf, $volid, 1);\n    return PVE::Tools::file_get_contents($full_path, 1 * 1024 * 1024);\n}\n\nmy $cloudinit_methods = {\n    configdrive2 => \\&generate_configdrive2,\n    nocloud => \\&generate_nocloud,\n    opennebula => \\&generate_opennebula,\n};\n\nsub has_changes {\n    my ($conf) = @_;\n\n    if (my $cloudinit = $conf->{'special-sections'}->{cloudinit}) {\n        return !!$cloudinit->%*;\n    }\n\n    return;\n}\n\nsub generate_cloudinit_config {\n    my ($conf, $vmid) = @_;\n\n    my $format = get_cloudinit_format($conf);\n\n    my $has_changes = has_changes($conf);\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);\n\n            return if !$volname || $volname !~ m/vm-$vmid-cloudinit/;\n\n            my $generator = $cloudinit_methods->{$format}\n                or die \"missing cloudinit methods for format '$format'\\n\";\n\n            $generator->($conf, $vmid, $drive, $volname, $storeid);\n        },\n    );\n\n    return $has_changes;\n}\n\nsub apply_cloudinit_config {\n    my ($conf, $vmid) = @_;\n\n    my $has_changes = generate_cloudinit_config($conf, $vmid);\n\n    if ($has_changes) {\n        delete $conf->{'special-sections'}->{cloudinit};\n        PVE::QemuConfig->write_config($vmid, $conf);\n        return 1;\n    }\n\n    return $has_changes;\n}\n\nsub dump_cloudinit_config {\n    my ($conf, $vmid, $type, $mask_password) = @_;\n\n    my $format = get_cloudinit_format($conf);\n\n    if ($type eq 'user') {\n        return cloudinit_userdata($conf, $vmid, $mask_password);\n    } elsif ($type eq 'network') {\n        if ($format eq 'nocloud') {\n            return nocloud_network($conf);\n        } else {\n            return configdrive2_network($conf);\n        }\n    } else { # metadata config\n        # Don't mask password here to get correct uuid.\n        my $user = cloudinit_userdata($conf, $vmid);\n        if ($format eq 'nocloud') {\n            my $network = nocloud_network($conf);\n            return nocloud_gen_metadata($user, $network);\n        } else {\n            my $network = configdrive2_network($conf);\n            return configdrive2_gen_metadata($user, $network);\n        }\n    }\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/DBusVMState.pm",
    "content": "package PVE::QemuServer::DBusVMState;\n\nuse strict;\nuse warnings;\n\nuse Net::DBus;\nuse Net::DBus::RemoteService;\n\nuse PVE::SafeSyslog;\nuse PVE::Systemd;\nuse PVE::Tools;\n\nuse PVE::QemuServer::Helpers;\n\nuse constant {\n    DBUS_VMSTATE_EXE => '/usr/libexec/qemu-server/dbus-vmstate',\n};\n\n# Call a method for an object from a specific interface name.\n# In contrast to calling the method directly by using $obj->Method(), this\n# actually respects the owner of the object and thus can be used for interfaces\n# with might have multiple (queued) owners on the DBus.\nmy sub dbus_call_method {\n    my ($obj, $interface, $method, $params, $timeout) = @_;\n\n    $timeout = 10 if !$timeout;\n\n    my $con = $obj->{service}->get_bus()->get_connection();\n\n    my $call = $con->make_method_call_message(\n        $obj->{service}->get_service_name(),\n        $obj->{object_path},\n        $interface,\n        $method,\n    );\n\n    $call->set_destination($obj->get_service()->get_owner_name());\n    $call->append_args_list($params->@*) if $params;\n\n    return $con->send_with_reply_and_block($call, $timeout * 1000)->get_args_list();\n}\n\n# Retrieves a property from an object from a specific interface name.\n# In contrast to accessing the property directly by using $obj->Property, this\n# actually respects the owner of the object and thus can be used for interfaces\n# with might have multiple (queued) owners on the DBus.\nmy sub dbus_get_property {\n    my ($obj, $interface, $name) = @_;\n\n    my @reply =\n        dbus_call_method($obj, 'org.freedesktop.DBus.Properties', 'Get', [$interface, $name]);\n    return $reply[0];\n}\n\n# Starts the dbus-vmstate helper D-Bus service daemon and adds the needed\n# object to the appropriate QEMU instance for the specified VM.\nsub qemu_add_dbus_vmstate {\n    my ($vmid) = @_;\n\n    if (!PVE::QemuServer::Helpers::vm_running_locally($vmid)) {\n        die \"VM $vmid must be running locally\\n\";\n    }\n\n    # In case some leftover, previous instance is running, stop it. Otherwise\n    # we run into errors, as a systemd service instance is unique.\n    if (defined(qemu_del_dbus_vmstate($vmid, quiet => 1))) {\n        warn \"stopped previously running dbus-vmstate helper for VM $vmid\\n\";\n    }\n\n    # Start the actual service, which will then register itself with QEMU.\n    eval { PVE::Tools::run_command(['systemctl', 'start', \"pve-dbus-vmstate\\@$vmid\"]) };\n    if (my $err = $@) {\n        die \"failed to start DBus VMState service for VM $vmid: $err\\n\";\n    }\n}\n\n# Stops the dbus-vmstate helper D-Bus service daemon and removes the associated\n# object from QEMU for the specified VM.\n#\n# Returns the number of migrated conntrack entries, or undef in case of error.\nsub qemu_del_dbus_vmstate {\n    my ($vmid, %params) = @_;\n\n    my $num_entries = undef;\n    my $dbus = eval { Net::DBus->system(); };\n    if (my $err = $@) {\n        # log fundamental error even if $params{quiet} is set\n        syslog('warn', \"failed to connect to DBus system bus: $err\");\n        return undef;\n    }\n\n    my $dbus_obj = eval { $dbus->get_bus_object(); };\n    if (my $err = $@) {\n        # log fundamental error even if $params{quiet} is set\n        syslog('warn', \"failed to get DBus bus object: $err\");\n        return undef;\n    }\n\n    my $owners = eval { $dbus_obj->ListQueuedOwners('org.qemu.VMState1') };\n    if (my $err = $@) {\n        syslog('warn', \"failed to retrieve org.qemu.VMState1 owners: $err\\n\")\n            if !$params{quiet};\n        return undef;\n    }\n\n    # Iterate through all name owners for 'org.qemu.VMState1' and compare\n    # the ID. If we found the corresponding one for $vmid, retrieve the\n    # `NumMigratedEntries` property and call the `Quit()` method on it.\n    # Any D-Bus interaction might die/croak, so try to be careful here and\n    # swallow any hard errors.\n    foreach my $owner (@$owners) {\n        my $service = eval { Net::DBus::RemoteService->new($dbus, $owner, 'org.qemu.VMState1') };\n        if (my $err = $@) {\n            syslog('warn', \"failed to get org.qemu.VMState1 service from D-Bus $owner: $err\\n\")\n                if !$params{quiet};\n            next;\n        }\n\n        my $object = eval { $service->get_object('/org/qemu/VMState1') };\n        if (my $err = $@) {\n            syslog('warn', \"failed to get /org/qemu/VMState1 object from D-Bus $owner: $err\\n\")\n                if !$params{quiet};\n            next;\n        }\n\n        my $id = eval { dbus_get_property($object, 'org.qemu.VMState1', 'Id') };\n        if (defined($id) && $id eq \"pve-vmstate-$vmid\") {\n            my $helperobj =\n                eval { $service->get_object('/org/qemu/VMState1', 'com.proxmox.VMStateHelper') };\n            if (my $err = $@) {\n                syslog(\n                    'warn',\n                    \"found dbus-vmstate helper, but does not implement com.proxmox.VMStateHelper? ($err)\\n\",\n                ) if !$params{quiet};\n                last;\n            }\n\n            $num_entries = eval {\n                dbus_get_property($object, 'com.proxmox.VMStateHelper', 'NumMigratedEntries');\n            };\n            # Quit() does QMP object-del which has a timeout of 60 seconds\n            eval { dbus_call_method($object, 'com.proxmox.VMStateHelper', 'Quit', [], 70); };\n            if (my $err = $@) {\n                syslog('warn', \"failed to call quit on dbus-vmstate for VM $vmid: $err\\n\")\n                    if !$params{quiet};\n            }\n\n            last;\n        }\n    }\n\n    return $num_entries;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Drive.pm",
    "content": "package PVE::QemuServer::Drive;\n\nuse strict;\nuse warnings;\n\nuse Storable qw(dclone);\n\nuse IO::File;\nuse List::Util qw(first);\n\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Storage;\nuse PVE::Storage::Common;\nuse PVE::JSONSchema qw(get_standard_option);\n\nuse PVE::QemuServer::Monitor qw(qsd_qmp_peer vm_qmp_peer);\n\nuse base qw(Exporter);\n\nour @EXPORT_OK = qw(\n    is_valid_drivename\n    checked_parse_volname\n    checked_volume_format\n    drive_is_cloudinit\n    drive_is_cdrom\n    parse_drive\n    print_drive\n    storage_allows_io_uring_default\n);\n\nmy $DROPPED_PROPERTIES = ['cyls', 'heads', 'secs', 'trans'];\n\nour $QEMU_FORMAT_RE = qr/raw|qcow|qcow2|qed|vmdk|cloop/;\n\nPVE::JSONSchema::register_standard_option(\n    'pve-qm-image-format',\n    {\n        type => 'string',\n        enum => [qw(raw qcow qed qcow2 vmdk cloop)],\n        description => \"The drive's backing file's data format.\",\n        optional => 1,\n    },\n);\n\n# Check that a volume can be used for image-related operations with QEMU, in\n# particular, attached as VM image or ISO, used for qemu-img, or (live-)imported.\n# NOTE Currently, this helper cannot be used for backups.\n# TODO allow configuring certain restrictions via $opts argument, e.g. expected vtype?\nsub checked_parse_volname {\n    my ($storecfg, $volid) = @_;\n\n    my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =\n        PVE::Storage::parse_volname($storecfg, $volid);\n\n    if ($vtype eq 'import') {\n        die \"unable to parse format for import volume '$volid'\\n\" if !$format;\n        if ($format =~ m/^ova\\+(.*)$/) {\n            my $extracted_format = $1;\n            die \"volume '$volid' - unknown import format '$format'\\n\"\n                if $extracted_format !~ m/^($QEMU_FORMAT_RE)$/;\n            return ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format);\n        }\n    }\n\n    # TODO PVE 9 - consider switching to die for an undefined format\n    $format = 'raw' if !defined($format);\n\n    die \"volume '$volid' - not a QEMU image format '$format'\\n\"\n        if $format !~ m/^($QEMU_FORMAT_RE)$/;\n\n    # For iso content type, no format is returned yet.\n\n    return ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format);\n}\n\nsub checked_volume_format {\n    my ($storecfg, $volid) = @_;\n\n    return (checked_parse_volname($storecfg, $volid))[6];\n}\n\nmy $cdrom_path;\n\nsub get_cdrom_path {\n    return $cdrom_path if defined($cdrom_path);\n\n    $cdrom_path = first { -l $_ } map { \"/dev/cdrom$_\" } ('', '1', '2');\n\n    if (!defined($cdrom_path)) {\n        log_warn(\"no physical CD-ROM available, ignoring\");\n        $cdrom_path = '';\n    }\n\n    return $cdrom_path;\n}\n\nsub get_iso_path {\n    my ($storecfg, $cdrom) = @_;\n\n    if ($cdrom eq 'cdrom') {\n        return get_cdrom_path();\n    } elsif ($cdrom eq 'none') {\n        return '';\n    } elsif ($cdrom =~ m|^/|) {\n        return $cdrom;\n    } else {\n        return PVE::Storage::path($storecfg, $cdrom);\n    }\n}\n\n# Returns the path that can be used on the QEMU commandline and in QMP commands as well as the\n# checked format of the drive.\nsub get_path_and_format {\n    my ($storecfg, $drive, $live_restore_name) = @_;\n\n    my $path;\n    my $volid = $drive->{file};\n    my $drive_id = get_drive_id($drive);\n\n    my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);\n\n    if (drive_is_cdrom($drive)) {\n        $path = get_iso_path($storecfg, $volid);\n        die \"$drive_id: cannot back cdrom drive with a live restore image\\n\" if $live_restore_name;\n    } else {\n        if ($storeid) {\n            $path = PVE::Storage::path($storecfg, $volid);\n        } else {\n            $path = $volid;\n        }\n    }\n\n    # For PVE-managed volumes, use the format from the storage layer and prevent overrides via the\n    # drive's 'format' option. For unmanaged volumes, fallback to 'raw' to avoid auto-detection by\n    # QEMU. For the special case 'none' (get_iso_path() returns an empty $path), there should be no\n    # format or QEMU won't start.\n    my $format;\n    if (drive_is_cdrom($drive) && !$path) {\n        # no format\n    } elsif ($storeid) {\n        $format = checked_volume_format($storecfg, $volid);\n\n        if ($drive->{format} && $drive->{format} ne $format) {\n            die \"drive '$drive_id' - volume '$volid' - 'format=$drive->{format}' option different\"\n                . \" from storage format '$format'\\n\";\n        }\n    } else {\n        $format = $drive->{format} // 'raw';\n    }\n\n    return ($path, $format);\n}\n\nmy $MAX_IDE_DISKS = 4;\nmy $MAX_SCSI_DISKS = 31;\nmy $MAX_VIRTIO_DISKS = 16;\nour $MAX_SATA_DISKS = 6;\nour $MAX_UNUSED_DISKS = 256;\nour $NEW_DISK_RE = qr!^(([^/:\\s]+):)?(\\d+(\\.\\d+)?)$!;\n\nour $drivedesc_hash;\n# Schema when disk allocation is possible.\nour $drivedesc_hash_with_alloc = {};\n\nmy %drivedesc_base = (\n    volume => { alias => 'file' },\n    file => {\n        type => 'string',\n        format => 'pve-volume-id-or-qm-path',\n        default_key => 1,\n        format_description => 'volume',\n        description => \"The drive's backing volume.\",\n    },\n    media => {\n        type => 'string',\n        enum => [qw(cdrom disk)],\n        description => \"The drive's media type.\",\n        default => 'disk',\n        optional => 1,\n    },\n    snapshot => {\n        type => 'boolean',\n        description => \"Controls qemu's snapshot mode feature.\"\n            . \" If activated, changes made to the disk are temporary and will\"\n            . \" be discarded when the VM is shutdown.\",\n        optional => 1,\n    },\n    cache => {\n        type => 'string',\n        enum => [qw(none writethrough writeback unsafe directsync)],\n        description => \"The drive's cache mode\",\n        optional => 1,\n    },\n    format => get_standard_option('pve-qm-image-format'),\n    size => {\n        type => 'string',\n        format => 'disk-size',\n        format_description => 'DiskSize',\n        description => \"Disk size. This is purely informational and has no effect.\",\n        optional => 1,\n    },\n    backup => {\n        type => 'boolean',\n        description => \"Whether the drive should be included when making backups.\",\n        optional => 1,\n    },\n    replicate => {\n        type => 'boolean',\n        description => 'Whether the drive should considered for replication jobs.',\n        optional => 1,\n        default => 1,\n    },\n    rerror => {\n        type => 'string',\n        enum => [qw(ignore report stop)],\n        description => 'Read error action.',\n        optional => 1,\n    },\n    werror => {\n        type => 'string',\n        enum => [qw(enospc ignore report stop)],\n        description => 'Write error action.',\n        optional => 1,\n    },\n    aio => {\n        type => 'string',\n        enum => [qw(native threads io_uring)],\n        description => 'AIO type to use.',\n        optional => 1,\n    },\n    discard => {\n        type => 'string',\n        enum => [qw(ignore on)],\n        description =>\n            'Controls whether to pass discard/trim requests to the underlying storage.',\n        optional => 1,\n    },\n    detect_zeroes => {\n        type => 'boolean',\n        description => 'Controls whether to detect and try to optimize writes of zeroes.',\n        optional => 1,\n    },\n    serial => {\n        type => 'string',\n        format => 'urlencoded',\n        format_description => 'serial',\n        maxLength => 20 * 3, # *3 since it's %xx url enoded\n        description => \"The drive's reported serial number, url-encoded, up to 20 bytes long.\",\n        optional => 1,\n    },\n    shared => {\n        type => 'boolean',\n        description => 'Mark this locally-managed volume as available on all nodes',\n        verbose_description =>\n            \"Mark this locally-managed volume as available on all nodes.\\n\\nWARNING: This option does not share the volume automatically, it assumes it is shared already!\",\n        optional => 1,\n        default => 0,\n    },\n);\n\nmy %iothread_fmt = (\n    iothread => {\n        type => 'boolean',\n        description => \"Whether to use iothreads for this drive\",\n        optional => 1,\n    },\n);\n\nmy %product_fmt = (\n    product => {\n        type => 'string',\n        pattern => '[A-Za-z0-9\\-_\\s]{,16}', # QEMU (8.1) will quietly only use 16 bytes\n        format_description => 'product',\n        description => \"The drive's product name, up to 16 bytes long.\",\n        optional => 1,\n    },\n);\n\nmy %vendor_fmt = (\n    vendor => {\n        type => 'string',\n        pattern => '[A-Za-z0-9\\-_\\s]{,8}', # QEMU (8.1) will quietly only use 8 bytes\n        format_description => 'vendor',\n        description => \"The drive's vendor name, up to 8 bytes long.\",\n        optional => 1,\n    },\n);\n\nmy %model_fmt = (\n    model => {\n        type => 'string',\n        format => 'urlencoded',\n        format_description => 'model',\n        maxLength => 40 * 3, # *3 since it's %xx url enoded\n        description => \"The drive's reported model name, url-encoded, up to 40 bytes long.\",\n        optional => 1,\n    },\n);\n\nmy %queues_fmt = (\n    queues => {\n        type => 'integer',\n        description => \"Number of queues.\",\n        minimum => 2,\n        optional => 1,\n    },\n);\n\nmy %readonly_fmt = (\n    ro => {\n        type => 'boolean',\n        description => \"Whether the drive is read-only.\",\n        optional => 1,\n    },\n);\n\nmy %scsiblock_fmt = (\n    scsiblock => {\n        type => 'boolean',\n        description =>\n            \"whether to use scsi-block for full passthrough of host block device\\n\\nWARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host\",\n        optional => 1,\n        default => 0,\n    },\n);\n\nmy %ssd_fmt = (\n    ssd => {\n        type => 'boolean',\n        description =>\n            \"Whether to expose this drive as an SSD, rather than a rotational hard disk.\",\n        optional => 1,\n    },\n);\n\nmy %wwn_fmt = (\n    wwn => {\n        type => 'string',\n        pattern => qr/^(0x)[0-9a-fA-F]{16}/,\n        format_description => 'wwn',\n        description =>\n            \"The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.\",\n        optional => 1,\n    },\n);\n\nmy $add_throttle_desc = sub {\n    my ($key, $type, $what, $unit, $longunit, $minimum) = @_;\n    my $d = {\n        type => $type,\n        format_description => $unit,\n        description => \"Maximum $what in $longunit.\",\n        optional => 1,\n    };\n    $d->{minimum} = $minimum if defined($minimum);\n    $drivedesc_base{$key} = $d;\n};\n# throughput: (leaky bucket)\n$add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes per second');\n$add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes per second');\n$add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes per second');\n$add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes per second');\n$add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes per second');\n$add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes per second');\n$add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations per second');\n$add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations per second');\n$add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations per second');\n\n# pools: (pool of IO before throttling starts taking effect)\n$add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes per second');\n$add_throttle_desc->(\n    'mbps_rd_max',\n    'number',\n    'unthrottled read pool',\n    'mbps',\n    'megabytes per second',\n);\n$add_throttle_desc->(\n    'mbps_wr_max',\n    'number',\n    'unthrottled write pool',\n    'mbps',\n    'megabytes per second',\n);\n$add_throttle_desc->(\n    'iops_max',\n    'integer',\n    'unthrottled r/w I/O pool',\n    'iops',\n    'operations per second',\n);\n$add_throttle_desc->(\n    'iops_rd_max',\n    'integer',\n    'unthrottled read I/O pool',\n    'iops',\n    'operations per second',\n);\n$add_throttle_desc->(\n    'iops_wr_max',\n    'integer',\n    'unthrottled write I/O pool',\n    'iops',\n    'operations per second',\n);\n\n# burst lengths\n$add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);\n$add_throttle_desc->(\n    'bps_rd_max_length',\n    'integer',\n    'length of read I/O bursts',\n    'seconds',\n    'seconds',\n    1,\n);\n$add_throttle_desc->(\n    'bps_wr_max_length',\n    'integer',\n    'length of write I/O bursts',\n    'seconds',\n    'seconds',\n    1,\n);\n$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);\n$add_throttle_desc->(\n    'iops_rd_max_length',\n    'integer',\n    'length of read I/O bursts',\n    'seconds',\n    'seconds',\n    1,\n);\n$add_throttle_desc->(\n    'iops_wr_max_length',\n    'integer',\n    'length of write I/O bursts',\n    'seconds',\n    'seconds',\n    1,\n);\n\n# legacy support\n$drivedesc_base{'bps_rd_length'} = { alias => 'bps_rd_max_length' };\n$drivedesc_base{'bps_wr_length'} = { alias => 'bps_wr_max_length' };\n$drivedesc_base{'iops_rd_length'} = { alias => 'iops_rd_max_length' };\n$drivedesc_base{'iops_wr_length'} = { alias => 'iops_wr_max_length' };\n\nmy $ide_fmt = {\n    %drivedesc_base, %model_fmt, %ssd_fmt, %wwn_fmt,\n};\nPVE::JSONSchema::register_format(\"pve-qm-ide\", $ide_fmt);\n\nmy $idedesc = {\n    optional => 1,\n    type => 'string',\n    format => $ide_fmt,\n    description => \"Use volume as IDE hard disk or CD-ROM (n is 0 to \"\n        . ($MAX_IDE_DISKS - 1) . \").\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-ide\", $idedesc);\n\nmy $scsi_fmt = {\n    %drivedesc_base,\n    %iothread_fmt,\n    %product_fmt,\n    %queues_fmt,\n    %readonly_fmt,\n    %scsiblock_fmt,\n    %ssd_fmt,\n    %vendor_fmt,\n    %wwn_fmt,\n};\nmy $scsidesc = {\n    optional => 1,\n    type => 'string',\n    format => $scsi_fmt,\n    description => \"Use volume as SCSI hard disk or CD-ROM (n is 0 to \"\n        . ($MAX_SCSI_DISKS - 1) . \").\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-scsi\", $scsidesc);\n\nmy $sata_fmt = {\n    %drivedesc_base, %ssd_fmt, %wwn_fmt,\n};\nmy $satadesc = {\n    optional => 1,\n    type => 'string',\n    format => $sata_fmt,\n    description => \"Use volume as SATA hard disk or CD-ROM (n is 0 to \"\n        . ($MAX_SATA_DISKS - 1) . \").\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-sata\", $satadesc);\n\nmy $virtio_fmt = {\n    %drivedesc_base, %iothread_fmt, %readonly_fmt,\n};\nmy $virtiodesc = {\n    optional => 1,\n    type => 'string',\n    format => $virtio_fmt,\n    description => \"Use volume as VIRTIO hard disk (n is 0 to \"\n        . ($MAX_VIRTIO_DISKS - 1) . \").\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-virtio\", $virtiodesc);\n\nmy %efitype_fmt = (\n    efitype => {\n        type => 'string',\n        enum => [qw(2m 4m)],\n        description => \"Size and type of the OVMF EFI vars. '4m' is newer and recommended,\"\n            . \" and required for Secure Boot. For backwards compatibility, '2m' is used\"\n            . \" if not otherwise specified. Ignored for VMs with arch=aarch64 (ARM).\",\n        optional => 1,\n        default => '2m',\n    },\n    'pre-enrolled-keys' => {\n        type => 'boolean',\n        description =>\n            \"Use am EFI vars template with distribution-specific and Microsoft Standard\"\n            . \" keys enrolled, if used with 'efitype=4m'. Note that this will enable Secure Boot by\"\n            . \" default, though it can still be turned off from within the VM.\",\n        optional => 1,\n        default => 0,\n    },\n    'ms-cert' => {\n        type => 'string',\n        enum => [qw(2011 2023 2023w 2023k)],\n        description =>\n            \"Informational marker indicating the version of the latest Microsoft UEFI certificates\"\n            . \" that have been enrolled by Proxmox VE. The value '2023k' means that the 'Microsoft\"\n            . \" UEFI CA 2023', the 'Windows UEFI CA 2023' and the 'Microsoft Corporation KEK 2K CA\"\n            . \" 2023' certificates are included. The values '2023' and '2023w' are\"\n            . \" deprecated and for compatibility only.\",\n        optional => 1,\n        default => '2011',\n    },\n);\n\nmy $efidisk_fmt = {\n    volume => { alias => 'file' },\n    file => {\n        type => 'string',\n        format => 'pve-volume-id-or-qm-path',\n        default_key => 1,\n        format_description => 'volume',\n        description => \"The drive's backing volume.\",\n    },\n    format => get_standard_option('pve-qm-image-format'),\n    size => {\n        type => 'string',\n        format => 'disk-size',\n        format_description => 'DiskSize',\n        description => \"Disk size. This is purely informational and has no effect.\",\n        optional => 1,\n    },\n    %efitype_fmt,\n};\n\nmy $efidisk_desc = {\n    optional => 1,\n    type => 'string',\n    format => $efidisk_fmt,\n    description => \"Configure a disk for storing EFI vars.\",\n};\n\nPVE::JSONSchema::register_standard_option(\"pve-qm-efidisk\", $efidisk_desc);\n\nmy %tpmversion_fmt = (\n    version => {\n        type => 'string',\n        enum => [qw(v1.2 v2.0)],\n        description => \"The TPM interface version. v2.0 is newer and should be preferred.\"\n            . \" Note that this cannot be changed later on.\",\n        optional => 1,\n        default => 'v1.2',\n    },\n);\nmy $tpmstate_fmt = {\n    volume => { alias => 'file' },\n    file => {\n        type => 'string',\n        format => 'pve-volume-id-or-qm-path',\n        default_key => 1,\n        format_description => 'volume',\n        description => \"The drive's backing volume.\",\n    },\n    format => get_standard_option('pve-vm-image-format', { optional => 1 }),\n    size => {\n        type => 'string',\n        format => 'disk-size',\n        format_description => 'DiskSize',\n        description => \"Disk size. This is purely informational and has no effect.\",\n        optional => 1,\n    },\n    %tpmversion_fmt,\n};\nmy $tpmstate_desc = {\n    optional => 1,\n    type => 'string',\n    format => $tpmstate_fmt,\n    description => \"Configure a Disk for storing TPM state. The format is fixed to 'raw'.\",\n};\nuse constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;\n\nmy $alldrive_fmt = {\n    %drivedesc_base,\n    %iothread_fmt,\n    %model_fmt,\n    %product_fmt,\n    %queues_fmt,\n    %readonly_fmt,\n    %scsiblock_fmt,\n    %ssd_fmt,\n    %vendor_fmt,\n    %wwn_fmt,\n    %tpmversion_fmt,\n    %efitype_fmt,\n};\n\nmy %import_from_fmt = (\n    'import-from' => {\n        type => 'string',\n        format => 'pve-volume-id-or-absolute-path',\n        format_description => 'source volume',\n        description => \"Create a new disk, importing from this source (volume ID or absolute \"\n            . \"path). When an absolute path is specified, it's up to you to ensure that the source \"\n            . \"is not actively used by another process during the import!\",\n        optional => 1,\n    },\n);\n\nmy $alldrive_fmt_with_alloc = {\n    %$alldrive_fmt, %import_from_fmt,\n};\n\nmy $unused_fmt = {\n    volume => { alias => 'file' },\n    file => {\n        type => 'string',\n        format => 'pve-volume-id',\n        default_key => 1,\n        format_description => 'volume',\n        description => \"The drive's backing volume.\",\n    },\n};\n\nmy $unuseddesc = {\n    optional => 1,\n    type => 'string',\n    format => $unused_fmt,\n    description =>\n        \"Reference to unused volumes. This is used internally, and should not be modified manually.\",\n};\n\nmy $with_alloc_desc_cache = {\n    unused => $unuseddesc, # Allocation for unused is not supported currently.\n};\nmy $desc_with_alloc = sub {\n    my ($type, $desc) = @_;\n\n    return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};\n\n    my $new_desc = dclone($desc);\n\n    $new_desc->{format}->{'import-from'} = $import_from_fmt{'import-from'};\n\n    my $extra_note = '';\n    if ($type eq 'efidisk') {\n        $extra_note =\n            \" Note that SIZE_IN_GiB is ignored here and that the default EFI vars are \"\n            . \"copied to the volume instead.\";\n    } elsif ($type eq 'tpmstate') {\n        $extra_note = \" Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.\";\n    }\n\n    $new_desc->{description} .=\n        \" Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new \"\n        . \"volume.${extra_note} Use STORAGE_ID:0 and the 'import-from' parameter to import from an \"\n        . \"existing volume.\";\n\n    $with_alloc_desc_cache->{$type} = $new_desc;\n\n    return $new_desc;\n};\n\nfor (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {\n    $drivedesc_hash->{\"ide$i\"} = $idedesc;\n    $drivedesc_hash_with_alloc->{\"ide$i\"} = $desc_with_alloc->('ide', $idedesc);\n}\n\nfor (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {\n    $drivedesc_hash->{\"sata$i\"} = $satadesc;\n    $drivedesc_hash_with_alloc->{\"sata$i\"} = $desc_with_alloc->('sata', $satadesc);\n}\n\nfor (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {\n    $drivedesc_hash->{\"scsi$i\"} = $scsidesc;\n    $drivedesc_hash_with_alloc->{\"scsi$i\"} = $desc_with_alloc->('scsi', $scsidesc);\n}\n\nfor (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {\n    $drivedesc_hash->{\"virtio$i\"} = $virtiodesc;\n    $drivedesc_hash_with_alloc->{\"virtio$i\"} = $desc_with_alloc->('virtio', $virtiodesc);\n}\n\n$drivedesc_hash->{efidisk0} = $efidisk_desc;\n$drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);\n\n$drivedesc_hash->{tpmstate0} = $tpmstate_desc;\n$drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);\n\nfor (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {\n    $drivedesc_hash->{\"unused$i\"} = $unuseddesc;\n    $drivedesc_hash_with_alloc->{\"unused$i\"} = $desc_with_alloc->('unused', $unuseddesc);\n}\n\nsub valid_drive_names_for_boot {\n    return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names();\n}\n\nsub valid_drive_names {\n    # order is important - used to autoselect boot disk\n    return (\n        (map { \"ide$_\" } (0 .. ($MAX_IDE_DISKS - 1))),\n        (map { \"scsi$_\" } (0 .. ($MAX_SCSI_DISKS - 1))),\n        (map { \"virtio$_\" } (0 .. ($MAX_VIRTIO_DISKS - 1))),\n        (map { \"sata$_\" } (0 .. ($MAX_SATA_DISKS - 1))),\n        'efidisk0',\n        'tpmstate0',\n    );\n}\n\nsub valid_drive_names_with_unused {\n    return (valid_drive_names(), map { \"unused$_\" } (0 .. ($MAX_UNUSED_DISKS - 1)));\n}\n\nsub is_valid_drivename {\n    my $dev = shift;\n\n    return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\\d+$/;\n}\n\nPVE::JSONSchema::register_format('pve-qm-bootdisk', \\&verify_bootdisk);\n\nsub verify_bootdisk {\n    my ($value, $noerr) = @_;\n\n    return $value if is_valid_drivename($value);\n\n    return if $noerr;\n\n    die \"invalid boot disk '$value'\\n\";\n}\n\nsub drive_is_cloudinit {\n    my ($drive) = @_;\n    return $drive->{file} =~ m@[:/](?:vm-\\d+-)?cloudinit(?:\\.$QEMU_FORMAT_RE)?$@;\n}\n\nsub drive_is_cdrom {\n    my ($drive, $exclude_cloudinit) = @_;\n\n    return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);\n\n    return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');\n}\n\nsub parse_drive_interface {\n    my ($key) = @_;\n\n    if ($key =~ m/^([^\\d]+)(\\d+)$/) {\n        return ($1, $2);\n    }\n\n    die \"unable to parse drive interface $key\\n\";\n}\n\n# ideX = [volume=]volume-id[,media=d]\n#        [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]\n#        [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]\n#        [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]\n#        [,iothread=on][,serial=serial][,model=model]\n\nsub parse_drive {\n    my ($key, $data, $with_alloc) = @_;\n\n    my ($interface, $index) = eval { parse_drive_interface($key) };\n    return if $@;\n\n    my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;\n\n    if (!defined($desc_hash->{$key})) {\n        warn \"invalid drive key: $key\\n\";\n        return;\n    }\n\n    my $desc = $desc_hash->{$key}->{format};\n    my $res = eval {\n        my $pps_opts = { skip => $DROPPED_PROPERTIES };\n        PVE::JSONSchema::parse_property_string($desc, $data, undef, undef, $pps_opts);\n    };\n    return if !$res;\n    $res->{interface} = $interface;\n    $res->{index} = $index;\n\n    my $error = 0;\n    foreach my $opt (qw(bps bps_rd bps_wr)) {\n        if (my $bps = defined(delete $res->{$opt})) {\n            if (defined($res->{\"m$opt\"})) {\n                warn \"both $opt and m$opt specified\\n\";\n                ++$error;\n                next;\n            }\n            $res->{\"m$opt\"} = sprintf(\"%.3f\", $bps / (1024 * 1024.0));\n        }\n    }\n\n    # can't use the schema's 'requires' because of the mbps* => bps* \"transforming aliases\"\n    for my $requirement (\n        [mbps_max => 'mbps'],\n        [mbps_rd_max => 'mbps_rd'],\n        [mbps_wr_max => 'mbps_wr'],\n        [miops_max => 'miops'],\n        [miops_rd_max => 'miops_rd'],\n        [miops_wr_max => 'miops_wr'],\n        [bps_max_length => 'mbps_max'],\n        [bps_rd_max_length => 'mbps_rd_max'],\n        [bps_wr_max_length => 'mbps_wr_max'],\n        [iops_max_length => 'iops_max'],\n        [iops_rd_max_length => 'iops_rd_max'],\n        [iops_wr_max_length => 'iops_wr_max'],\n    ) {\n        my ($option, $requires) = @$requirement;\n        if ($res->{$option} && !$res->{$requires}) {\n            warn \"$option requires $requires\\n\";\n            ++$error;\n        }\n    }\n\n    return if $error;\n\n    return if $res->{mbps_rd} && $res->{mbps};\n    return if $res->{mbps_wr} && $res->{mbps};\n    return if $res->{iops_rd} && $res->{iops};\n    return if $res->{iops_wr} && $res->{iops};\n\n    if ($res->{media} && ($res->{media} eq 'cdrom')) {\n        return if $res->{snapshot} || $res->{format};\n        return if $res->{interface} eq 'virtio';\n    }\n\n    if (my $size = $res->{size}) {\n        return if !defined($res->{size} = PVE::JSONSchema::parse_size($size));\n    }\n\n    return $res;\n}\n\nsub print_drive {\n    my ($drive, $with_alloc) = @_;\n    my $skip = ['index', 'interface'];\n    my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;\n    return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);\n}\n\nsub get_drive_id {\n    my ($drive) = @_;\n\n    die \"get_drive_id: no interface\\n\" if !defined($drive->{interface});\n    die \"get_drive_id: no index\\n\" if !defined($drive->{index});\n\n    return \"$drive->{interface}$drive->{index}\";\n}\n\nsub get_bootdisks {\n    my ($conf) = @_;\n\n    my $bootcfg;\n    $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $conf->{boot})\n        if $conf->{boot};\n\n    if (!defined($bootcfg) || $bootcfg->{legacy}) {\n        return [$conf->{bootdisk}] if $conf->{bootdisk};\n        return [];\n    }\n\n    my @list = PVE::Tools::split_list($bootcfg->{order});\n    @list = grep { is_valid_drivename($_) } @list;\n    return \\@list;\n}\n\nsub bootdisk_size {\n    my ($storecfg, $conf) = @_;\n\n    my $bootdisks = get_bootdisks($conf);\n    return if !@$bootdisks;\n    for my $bootdisk (@$bootdisks) {\n        next if !is_valid_drivename($bootdisk);\n        next if !$conf->{$bootdisk};\n        my $drive = parse_drive($bootdisk, $conf->{$bootdisk});\n        next if !defined($drive);\n        next if drive_is_cdrom($drive);\n        my $volid = $drive->{file};\n        next if !$volid;\n        return $drive->{size};\n    }\n\n    return;\n}\n\nsub update_disksize {\n    my ($drive, $newsize) = @_;\n\n    return if !defined($newsize);\n\n    my $oldsize = $drive->{size} // 0;\n\n    if ($newsize != $oldsize) {\n        $drive->{size} = $newsize;\n\n        my $old_fmt = PVE::JSONSchema::format_size($oldsize);\n        my $new_fmt = PVE::JSONSchema::format_size($newsize);\n\n        my $msg = \"size of disk '$drive->{file}' updated from $old_fmt to $new_fmt\";\n\n        return ($drive, $msg);\n    }\n\n    return;\n}\n\nsub is_volume_in_use {\n    my ($storecfg, $conf, $skip_drive, $volid) = @_;\n\n    my $path = PVE::Storage::path($storecfg, $volid);\n\n    my $scan_config = sub {\n        my ($cref) = @_;\n\n        foreach my $key (keys %$cref) {\n            my $value = $cref->{$key};\n            if (is_valid_drivename($key)) {\n                next if $skip_drive && $key eq $skip_drive;\n                my $drive = parse_drive($key, $value);\n                next if !$drive || !$drive->{file} || drive_is_cdrom($drive);\n                return 1 if $volid eq $drive->{file};\n                if ($drive->{file} =~ m!^/!) {\n                    return 1 if $drive->{file} eq $path;\n                } else {\n                    my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);\n                    next if !$storeid;\n                    my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);\n                    next if !$scfg;\n                    return 1 if $path eq PVE::Storage::path($storecfg, $drive->{file});\n                }\n            }\n        }\n\n        return 0;\n    };\n\n    return 1 if &$scan_config($conf);\n\n    undef $skip_drive;\n\n    for my $snap (values %{ $conf->{snapshots} }) {\n        return 1 if $scan_config->($snap);\n    }\n\n    return 0;\n}\n\nsub resolve_first_disk {\n    my ($conf, $cdrom) = @_;\n    my @disks = valid_drive_names_for_boot();\n    foreach my $ds (@disks) {\n        next if !$conf->{$ds};\n        my $disk = parse_drive($ds, $conf->{$ds});\n        next if drive_is_cdrom($disk) xor $cdrom;\n        return $ds;\n    }\n    return;\n}\n\nsub scsi_inquiry {\n    my ($fh, $noerr) = @_;\n\n    my $SG_IO = 0x2285;\n    my $SG_GET_VERSION_NUM = 0x2282;\n\n    my $versionbuf = \"\\x00\" x 8;\n    my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);\n    if (!$ret) {\n        die \"scsi ioctl SG_GET_VERSION_NUM failoed - $!\\n\" if !$noerr;\n        return;\n    }\n    my $version = unpack(\"I\", $versionbuf);\n    if ($version < 30000) {\n        die \"scsi generic interface too old\\n\" if !$noerr;\n        return;\n    }\n\n    my $buf = \"\\x00\" x 36;\n    my $sensebuf = \"\\x00\" x 8;\n    my $cmd = pack(\"C x3 C x1\", 0x12, 36);\n\n    # see /usr/include/scsi/sg.h\n    my $sg_io_hdr_t = \"i i C C s I P P P I I i P C C C C S S i I I\";\n\n    my $packet = pack(\n        $sg_io_hdr_t,\n        ord('S'),\n        -3,\n        length($cmd),\n        length($sensebuf),\n        0,\n        length($buf),\n        $buf,\n        $cmd,\n        $sensebuf,\n        6000,\n    );\n\n    $ret = ioctl($fh, $SG_IO, $packet);\n    if (!$ret) {\n        die \"scsi ioctl SG_IO failed - $!\\n\" if !$noerr;\n        return;\n    }\n\n    my @res = unpack($sg_io_hdr_t, $packet);\n    if ($res[17] || $res[18]) {\n        die \"scsi ioctl SG_IO status error - $!\\n\" if !$noerr;\n        return;\n    }\n\n    my $res = {};\n    $res->@{qw(type removable vendor product revision)} = unpack(\"C C x6 A8 A16 A4\", $buf);\n\n    $res->{removable} = $res->{removable} & 128 ? 1 : 0;\n    $res->{type} &= 0x1F;\n\n    return $res;\n}\n\nsub path_is_scsi {\n    my ($path) = @_;\n\n    my $fh = IO::File->new(\"+<$path\") || return;\n    my $res = scsi_inquiry($fh, 1);\n    close($fh);\n\n    return $res;\n}\n\nsub get_scsi_device_type {\n    my ($drive, $storecfg, $machine_version) = @_;\n\n    my $devicetype = 'hd';\n    my $path = '';\n    if (drive_is_cdrom($drive) || drive_is_cloudinit($drive)) {\n        $devicetype = 'cd';\n    } else {\n        if ($drive->{file} =~ m|^/|) {\n            $path = $drive->{file};\n            if (my $info = path_is_scsi($path)) {\n                if ($info->{type} == 0 && $drive->{scsiblock}) {\n                    $devicetype = 'block';\n                } elsif ($info->{type} == 1) { # tape\n                    $devicetype = 'generic';\n                }\n            }\n        } elsif ($drive->{file} =~ $NEW_DISK_RE) {\n            # special syntax cannot be parsed to path\n            return $devicetype;\n        } else {\n            $path = PVE::Storage::path($storecfg, $drive->{file});\n        }\n\n        # for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)\n        if (\n            $path =~ m/^iscsi\\:\\/\\//\n            && !PVE::QemuServer::Helpers::min_version($machine_version, 4, 1)\n        ) {\n            $devicetype = 'generic';\n        }\n    }\n\n    return $devicetype;\n}\n\nsub storage_allows_io_uring_default {\n    my ($scfg, $cache_direct) = @_;\n\n    # io_uring with cache mode writeback or writethrough on krbd will hang...\n    return if $scfg && $scfg->{type} eq 'rbd' && $scfg->{krbd} && !$cache_direct;\n\n    # io_uring with cache mode writeback or writethrough on LVM will hang, without cache only\n    # sometimes, just plain disable...\n    return if $scfg && $scfg->{type} eq 'lvm';\n\n    # io_uring causes problems when used with CIFS since kernel 5.15\n    # Some discussion: https://www.spinics.net/lists/linux-cifs/msg26734.html\n    return if $scfg && $scfg->{type} eq 'cifs';\n\n    return 1;\n}\n\nsub drive_uses_cache_direct {\n    my ($drive, $scfg) = @_;\n\n    my $cache_direct = 0;\n\n    if (my $cache = $drive->{cache}) {\n        $cache_direct = $cache =~ /^(?:off|none|directsync)$/;\n    } elsif (!drive_is_cdrom($drive) && !($scfg && $scfg->{type} eq 'btrfs' && !$scfg->{nocow})) {\n        $cache_direct = 1;\n    }\n\n    return $cache_direct;\n}\n\nsub aio_cmdline_option {\n    my ($scfg, $drive, $cache_direct) = @_;\n\n    return $drive->{aio} if $drive->{aio};\n\n    if (storage_allows_io_uring_default($scfg, $cache_direct)) {\n        # io_uring supports all cache modes\n        return 'io_uring';\n    } else {\n        # aio native works only with O_DIRECT\n        if ($cache_direct) {\n            return 'native';\n        } else {\n            return 'threads';\n        }\n    }\n}\n\n# must not be called for CD-ROMs\nsub detect_zeroes_cmdline_option {\n    my ($drive) = @_;\n\n    die \"cannot use detect-zeroes for CD-ROM\\n\" if drive_is_cdrom($drive);\n\n    if (defined($drive->{detect_zeroes}) && !$drive->{detect_zeroes}) {\n        return 'off';\n    } elsif ($drive->{discard}) {\n        return $drive->{discard} eq 'on' ? 'unmap' : 'on';\n    }\n\n    # This used to be our default with discard not being specified:\n    return 'on';\n}\n\nsub drive_uses_qsd_fuse {\n    my ($storecfg, $drive) = @_;\n\n    if ($drive->{interface} eq 'tpmstate') {\n        my ($storeid) = PVE::Storage::parse_volume_id($drive->{file}, 1);\n        my $format = checked_volume_format($storecfg, $drive->{file});\n        return $storeid && $format ne 'raw';\n    }\n\n    return;\n}\n\nsub drive_qmp_peer {\n    my ($storecfg, $vmid, $drive) = @_;\n\n    return drive_uses_qsd_fuse($storecfg, $drive) ? qsd_qmp_peer($vmid) : vm_qmp_peer($vmid);\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/DriveDevice.pm",
    "content": "package PVE::QemuServer::DriveDevice;\n\nuse strict;\nuse warnings;\n\nuse URI::Escape;\n\nuse PVE::QemuServer::Drive qw (drive_is_cdrom);\nuse PVE::QemuServer::Helpers qw(kvm_user_version min_version);\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::PCI qw(print_pci_addr);\n\nuse base qw(Exporter);\n\nour @EXPORT_OK = qw(\n    print_drivedevice_full\n    scsihw_infos\n);\n\nsub scsihw_infos {\n    my ($scsihw, $drive_index) = @_;\n\n    my $maxdev = 0;\n\n    if (!$scsihw || ($scsihw =~ m/^lsi/)) {\n        $maxdev = 7;\n    } elsif ($scsihw && ($scsihw eq 'virtio-scsi-single')) {\n        $maxdev = 1;\n    } else {\n        $maxdev = 256;\n    }\n\n    my $controller = int($drive_index / $maxdev);\n    my $controller_prefix =\n        ($scsihw && $scsihw eq 'virtio-scsi-single')\n        ? \"virtioscsi\"\n        : \"scsihw\";\n\n    return ($maxdev, $controller, $controller_prefix);\n}\n\nsub print_drivedevice_full {\n    my ($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type) = @_;\n\n    my $device = '';\n    my $maxdev = 0;\n\n    my $machine_version =\n        PVE::QemuServer::Machine::extract_version($machine_type, kvm_user_version());\n    my $has_write_cache = 1; # whether the device has a 'write-cache' option\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n    if ($drive->{interface} eq 'virtio') {\n        my $pciaddr = print_pci_addr(\"$drive_id\", $bridges, $arch);\n        $device = 'virtio-blk-pci';\n        # for the switch to -blockdev, there is no blockdev for 'none'\n        if (!min_version($machine_version, 10, 0) || $drive->{file} ne 'none') {\n            $device .= \",drive=drive-$drive_id\";\n        }\n        $device .= \",id=${drive_id}${pciaddr}\";\n        $device .= \",iothread=iothread-$drive_id\" if $drive->{iothread};\n    } elsif ($drive->{interface} eq 'scsi') {\n\n        my ($maxdev, $controller, $controller_prefix) =\n            scsihw_infos($conf->{scsihw}, $drive->{index});\n        my $unit = $drive->{index} % $maxdev;\n\n        my $device_type =\n            PVE::QemuServer::Drive::get_scsi_device_type($drive, $storecfg, $machine_version);\n\n        if (!$conf->{scsihw} || $conf->{scsihw} =~ m/^lsi/ || $conf->{scsihw} eq 'pvscsi') {\n            $device = \"scsi-$device_type,bus=$controller_prefix$controller.0,scsi-id=$unit\";\n        } else {\n            $device = \"scsi-$device_type,bus=$controller_prefix$controller.0,channel=0,scsi-id=0\"\n                . \",lun=$drive->{index}\";\n        }\n        # for the switch to -blockdev, there is no blockdev for 'none'\n        if (!min_version($machine_version, 10, 0) || $drive->{file} ne 'none') {\n            $device .= \",drive=drive-$drive_id\";\n        }\n        $device .= \",id=$drive_id\";\n\n        # For the switch to -blockdev, the SCSI device ID needs to be explicitly specified. Note\n        # that only ide-cd and ide-hd have a 'device_id' option.\n        if (\n            min_version($machine_version, 10, 0) && ($device_type eq 'cd' || $device_type eq 'hd')\n        ) {\n            $device .= \",device_id=drive-${drive_id}\";\n        }\n\n        if ($drive->{ssd} && ($device_type eq 'block' || $device_type eq 'hd')) {\n            $device .= \",rotation_rate=1\";\n        }\n        $device .= \",wwn=$drive->{wwn}\" if $drive->{wwn};\n\n        # only scsi-hd and scsi-cd support passing vendor and product information and have a\n        # 'write-cache' option\n        if ($device_type eq 'hd' || $device_type eq 'cd') {\n            if (my $vendor = $drive->{vendor}) {\n                $device .= \",vendor=$vendor\";\n            }\n            if (my $product = $drive->{product}) {\n                $device .= \",product=$product\";\n            }\n\n            $has_write_cache = 1;\n        } else {\n            $has_write_cache = 0;\n        }\n\n    } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {\n        my $maxdev = ($drive->{interface} eq 'sata') ? $PVE::QemuServer::Drive::MAX_SATA_DISKS : 2;\n        my $controller = int($drive->{index} / $maxdev);\n        my $unit = $drive->{index} % $maxdev;\n\n        # machine type q35 only supports unit=0 for IDE rather than 2 units. This wasn't handled\n        # correctly before, so e.g. index=2 was mapped to controller=1,unit=0 rather than\n        # controller=2,unit=0. Note that odd indices never worked, as they would be mapped to\n        # unit=1, so to keep backwards compat for migration, it suffices to keep even ones as they\n        # were before. Move odd ones up by 2 where they don't clash.\n        if (PVE::QemuServer::Machine::machine_type_is_q35($conf) && $drive->{interface} eq 'ide') {\n            $controller += 2 * ($unit % 2);\n            $unit = 0;\n        }\n\n        my $device_type = ($drive->{media} && $drive->{media} eq 'cdrom') ? \"cd\" : \"hd\";\n\n        # With ide-hd, the inserted block node needs to be marked as writable too, but -blockdev\n        # will complain if it's marked as writable but the actual backing device is read-only (e.g.\n        # read-only base LV). IDE/SATA do not support being configured as read-only, the most\n        # similar is using ide-cd instead of ide-hd, with most of the code and configuration shared\n        # in QEMU. Since a template is never actually started, the front-end device is never\n        # accessed. The backup only accesses the inserted block node, so it does not matter for the\n        # backup if the type is 'ide-cd' instead.\n        $device_type = 'cd' if $conf->{template};\n\n        $device = \"ide-$device_type\";\n        if ($drive->{interface} eq 'ide') {\n            $device .= \",bus=ide.$controller,unit=$unit\";\n        } else {\n            $device .= \",bus=ahci$controller.$unit\";\n        }\n        if (!min_version($machine_version, 10, 0) || $drive->{file} ne 'none') {\n            $device .= \",drive=drive-$drive_id\";\n        }\n        $device .= \",id=$drive_id\";\n\n        if ($device_type eq 'hd') {\n            if (my $model = $drive->{model}) {\n                $model = URI::Escape::uri_unescape($model);\n                $device .= \",model=$model\";\n            }\n            if ($drive->{ssd}) {\n                $device .= \",rotation_rate=1\";\n            }\n        }\n        $device .= \",wwn=$drive->{wwn}\" if $drive->{wwn};\n    } elsif ($drive->{interface} eq 'usb') {\n        die \"implement me\";\n        #  -device ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0\n    } else {\n        die \"unsupported interface type\";\n    }\n\n    $device .= \",bootindex=$drive->{bootindex}\" if $drive->{bootindex};\n\n    if (my $serial = $drive->{serial}) {\n        $serial = URI::Escape::uri_unescape($serial);\n        $device .= \",serial=$serial\";\n    }\n\n    if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev\n        if (!drive_is_cdrom($drive) && $has_write_cache) {\n            my $write_cache = 'on';\n            if (my $cache = $drive->{cache}) {\n                $write_cache = 'off' if $cache eq 'writethrough' || $cache eq 'directsync';\n            }\n            $device .= \",write-cache=$write_cache\";\n        }\n        for my $o (qw(rerror werror)) {\n            $device .= \",$o=$drive->{$o}\" if defined($drive->{$o});\n        }\n    }\n\n    return $device;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Helpers.pm",
    "content": "package PVE::QemuServer::Helpers;\n\nuse strict;\nuse warnings;\n\nuse File::stat;\nuse IO::File;\nuse JSON;\n\nuse PVE::Cluster;\nuse PVE::INotify;\nuse PVE::ProcFSTools;\nuse PVE::Tools;\n\nuse base 'Exporter';\nour @EXPORT_OK = qw(\n    min_version\n    config_aware_timeout\n    get_iscsi_initiator_name\n    kvm_user_version\n    parse_number_sets\n    windows_version\n    get_host_arch\n);\n\nmy $nodename = PVE::INotify::nodename();\n\nmy $arch_to_qemu_binary = {\n    aarch64 => '/usr/bin/qemu-system-aarch64',\n    x86_64 => '/usr/bin/qemu-system-x86_64',\n};\n\n# wrapper around the Tools helper, having it here makes it easier to mock for testing\nsub get_host_arch {\n    return PVE::Tools::get_host_arch();\n}\n\nsub get_command_for_arch($) {\n    my ($arch) = @_;\n    return '/usr/bin/kvm' if get_host_arch() eq $arch; # i.e. native arch\n\n    my $cmd = $arch_to_qemu_binary->{$arch}\n        or die \"don't know how to emulate architecture '$arch'\\n\";\n    return $cmd;\n}\n\nsub get_vm_arch {\n    my ($conf) = @_;\n    return $conf->{arch} // get_host_arch();\n}\n\nmy $kvm_user_version = {};\nmy $kvm_mtime = {};\n\nsub kvm_user_version {\n    my ($binary) = @_;\n\n    $binary //= get_command_for_arch(get_host_arch()); # get the native arch by default\n    my $st = stat($binary);\n\n    my $cachedmtime = $kvm_mtime->{$binary} // -1;\n    return $kvm_user_version->{$binary}\n        if $kvm_user_version->{$binary}\n        && $cachedmtime == $st->mtime;\n\n    $kvm_user_version->{$binary} = 'unknown';\n    $kvm_mtime->{$binary} = $st->mtime;\n\n    my $code = sub {\n        my $line = shift;\n        if ($line =~ m/^QEMU( PC)? emulator version (\\d+\\.\\d+(\\.\\d+)?)(\\.\\d+)?[,\\s]/) {\n            $kvm_user_version->{$binary} = $2;\n        }\n    };\n\n    eval { PVE::Tools::run_command([$binary, '--version'], outfunc => $code); };\n    warn $@ if $@;\n\n    return $kvm_user_version->{$binary};\n}\n\n# Paths and directories\n\nour $var_run_tmpdir = \"/var/run/qemu-server\";\nmkdir $var_run_tmpdir;\n\nsub qmp_socket {\n    my ($peer) = @_;\n    my ($id, $type) = $peer->@{qw(id type)};\n    return \"${var_run_tmpdir}/${id}.${type}\";\n}\n\nsub qsd_pidfile_name {\n    my ($id) = @_;\n    return \"${var_run_tmpdir}/qsd-${id}.pid\";\n}\n\nsub qsd_fuse_export_cleanup_files {\n    my ($id) = @_;\n\n    # Usually, /var/run is a symlink to /run. It needs to be the exact path for checking if mounted\n    # below. Note that Cwd::realpath() needs to be done on the directory already. Doing it on the\n    # file does not work if the storage daemon is not running and the FUSE is still mounted.\n    my ($real_dir) = Cwd::realpath($var_run_tmpdir) =~ m/^(.*)$/; # untaint\n    if (!$real_dir) {\n        warn \"error resolving $var_run_tmpdir - not checking for left-over QSD files\\n\";\n        return;\n    }\n\n    my $mounts = PVE::ProcFSTools::parse_proc_mounts();\n\n    PVE::Tools::dir_glob_foreach(\n        $real_dir,\n        \"qsd-${id}-.*\\.fuse\",\n        sub {\n            my ($file) = @_;\n            my $path = \"${real_dir}/${file}\";\n            if (grep { $_->[1] eq $path } $mounts->@*) {\n                PVE::Tools::run_command(['umount', $path]);\n            }\n            unlink $path;\n        },\n    );\n}\n\nsub qsd_fuse_export_path {\n    my ($id, $export_name) = @_;\n    return \"${var_run_tmpdir}/qsd-${id}-${export_name}.fuse\";\n}\n\nsub vm_pidfile_name {\n    my ($vmid) = @_;\n    return \"${var_run_tmpdir}/$vmid.pid\";\n}\n\nsub vnc_socket {\n    my ($vmid) = @_;\n    return \"${var_run_tmpdir}/$vmid.vnc\";\n}\n\n# Parse the cmdline of a running kvm/qemu-* process and return arguments as hash\nsub parse_cmdline {\n    my ($pid) = @_;\n\n    my $fh = IO::File->new(\"/proc/$pid/cmdline\", \"r\");\n    if (defined($fh)) {\n        my $line = <$fh>;\n        $fh->close;\n        return if !$line;\n        my @param = split(/\\0/, $line);\n\n        my $cmd = $param[0];\n        return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m@(?:^|/)qemu-[^/]+$@);\n\n        my $phash = {};\n        my $pending_cmd;\n        for (my $i = 0; $i < scalar(@param); $i++) {\n            my $p = $param[$i];\n            next if !$p;\n\n            if ($p =~ m/^--?(.*)$/) {\n                if ($pending_cmd) {\n                    $phash->{$pending_cmd} = {};\n                }\n                $pending_cmd = $1;\n            } elsif ($pending_cmd) {\n                $phash->{$pending_cmd} = { value => $p };\n                $pending_cmd = undef;\n            }\n        }\n\n        return $phash;\n    }\n    return;\n}\n\nmy sub instance_running_locally {\n    my ($pidfile) = @_;\n\n    if (my $fd = IO::File->new(\"<$pidfile\")) {\n        my $st = stat($fd);\n        my $line = <$fd>;\n        close($fd);\n\n        my $mtime = $st->mtime;\n        if ($mtime > time()) {\n            warn \"file '$pidfile' modified in future\\n\";\n        }\n\n        if ($line =~ m/^(\\d+)$/) {\n            my $pid = $1;\n            my $cmdline = parse_cmdline($pid);\n            if (\n                $cmdline\n                && defined($cmdline->{pidfile})\n                && $cmdline->{pidfile}->{value}\n                && $cmdline->{pidfile}->{value} eq $pidfile\n            ) {\n                if (my $pinfo = PVE::ProcFSTools::check_process_running($pid)) {\n                    return $pid;\n                }\n            }\n        }\n    }\n\n    return;\n}\n\nsub qsd_running_locally {\n    my ($id) = @_;\n\n    my $pidfile = qsd_pidfile_name($id);\n\n    return instance_running_locally($pidfile);\n}\n\nsub vm_running_locally {\n    my ($vmid) = @_;\n\n    my $pidfile = vm_pidfile_name($vmid);\n\n    return instance_running_locally($pidfile);\n}\n\nsub min_version {\n    my ($verstr, $major, $minor, $pve) = @_;\n\n    if ($verstr =~ m/^(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:\\+pve(\\d+))?/) {\n        return 1 if version_cmp($1, $major, $2, $minor, $4, $pve) >= 0;\n        return 0;\n    }\n\n    die \"internal error: cannot check version of invalid string '$verstr'\";\n}\n\n# gets in pairs the versions you want to compares, i.e.:\n# ($a-major, $b-major, $a-minor, $b-minor, $a-extra, $b-extra, ...)\n# returns 0 if same, -1 if $a is older than $b, +1 if $a is newer than $b\nsub version_cmp {\n    my @versions = @_;\n\n    my $size = scalar(@versions);\n\n    return 0 if $size == 0;\n\n    if ($size & 1) {\n        my (undef, $fn, $line) = caller(0);\n        die \"cannot compare odd count of versions, called from $fn:$line\\n\";\n    }\n\n    for (my $i = 0; $i < $size; $i += 2) {\n        my ($left, $right) = splice(@versions, 0, 2);\n        $left //= 0;\n        $right //= 0;\n\n        return 1 if $left > $right;\n        return -1 if $left < $right;\n    }\n    return 0;\n}\n\nsub config_aware_timeout {\n    my ($config, $memory, $is_suspended) = @_;\n    my $timeout = 30;\n\n    # Based on user reported startup time for vm with 512GiB @ 4-5 minutes\n    if (defined($memory) && $memory > 30720) {\n        $timeout = int($memory / 1024);\n    }\n\n    # When using PCI passthrough, users reported much higher startup times,\n    # growing with the amount of memory configured. Constant factor chosen\n    # based on user reports.\n    if (grep(/^hostpci[0-9]+$/, keys %$config)) {\n        $timeout *= 4;\n    }\n\n    if ($is_suspended && $timeout < 300) {\n        $timeout = 300;\n    }\n\n    if ($config->{hugepages} && $timeout < 150) {\n        $timeout = 150;\n    }\n\n    # Some testing showed that adding a NIC increased the start time by ~450ms\n    # consistently across different NIC models, options and already existing\n    # number of NICs.\n    # So 10x that to account for any potential system differences seemed\n    # reasonable. User reports with real-life values (20+: ~50s, 25: 45s, 17: 42s)\n    # also make this seem a good value.\n    my $nic_count = scalar(grep { /^net\\d+/ } keys %{$config});\n    $timeout += $nic_count * 5;\n\n    return $timeout;\n}\n\nsub get_node_pvecfg_version {\n    my ($node) = @_;\n\n    my $nodes_version_info = PVE::Cluster::get_node_kv('version-info', $node);\n    return if !$nodes_version_info->{$node};\n\n    my $version_info = decode_json($nodes_version_info->{$node});\n    return $version_info->{version};\n}\n\nsub pvecfg_min_version {\n    my ($verstr, $major, $minor, $release) = @_;\n\n    return 0 if !$verstr;\n\n    if ($verstr =~ m/^(\\d+)\\.(\\d+)(?:[.-](\\d+))?/) {\n        return 1 if version_cmp($1, $major, $2, $minor, $3 // 0, $release) >= 0;\n        return 0;\n    }\n\n    die \"internal error: cannot check version of invalid string '$verstr'\";\n}\n\nsub parse_number_sets {\n    my ($set) = @_;\n    my $res = [];\n    foreach my $part (split(/;/, $set)) {\n        if ($part =~ /^\\s*(\\d+)(?:-(\\d+))?\\s*$/) {\n            die \"invalid range: $part ($2 < $1)\\n\" if defined($2) && $2 < $1;\n            push @$res, [$1, $2];\n        } else {\n            die \"invalid range: $part\\n\";\n        }\n    }\n    return $res;\n}\n\nsub windows_version {\n    my ($ostype) = @_;\n\n    return 0 if !$ostype;\n\n    my $winversion = 0;\n\n    if ($ostype eq 'wxp' || $ostype eq 'w2k3' || $ostype eq 'w2k') {\n        $winversion = 5;\n    } elsif ($ostype eq 'w2k8' || $ostype eq 'wvista') {\n        $winversion = 6;\n    } elsif ($ostype =~ m/^win(\\d+)$/) {\n        $winversion = $1;\n    }\n\n    return $winversion;\n}\n\nsub needs_extraction {\n    my ($vtype, $fmt) = @_;\n    return $vtype eq 'import' && $fmt =~ m/^ova\\+(.*)$/;\n}\n\nsub get_iscsi_initiator_name {\n    my $initiator;\n\n    my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return;\n    while (defined(my $line = <$fh>)) {\n        next if $line !~ m/^\\s*InitiatorName\\s*=\\s*([\\.\\-:\\w]+)/;\n        $initiator = $1;\n        last;\n    }\n    $fh->close();\n\n    return $initiator;\n}\n\nmy $_host_bits;\n\nsub get_host_phys_address_bits {\n    return $_host_bits if defined($_host_bits);\n\n    my $fh = IO::File->new('/proc/cpuinfo', \"r\") or return;\n    while (defined(my $line = <$fh>)) {\n        # hopefully we never need to care about mixed (big.LITTLE) archs\n        if ($line =~ m/^address sizes\\s*:\\s*(\\d+)\\s*bits physical/i) {\n            $_host_bits = int($1);\n            $fh->close();\n            return $_host_bits;\n        }\n    }\n    $fh->close();\n    return; # undef, cannot really do anything..\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/ImportDisk.pm",
    "content": "package PVE::QemuServer::ImportDisk;\n\nuse strict;\nuse warnings;\n\nuse PVE::Storage;\nuse PVE::Tools qw(run_command extract_param);\n\nuse PVE::QemuConfig;\nuse PVE::QemuServer;\nuse PVE::QemuServer::QemuImage;\n\n# imports an external disk image to an existing VM\n# and creates by default a drive entry unused[n] pointing to the created volume\n# $params->{drive_name} may be used to specify ide0, scsi1, etc ...\n# $params->{format} may be used to specify qcow2, raw, etc ...\n# $params->{skiplock} may be used to skip checking for a lock in the VM config\n# $params->{'skip-config-update'} may be used to import the disk without updating the VM config\nsub do_import {\n    my ($src_path, $src_size, $vmid, $storage_id, $params) = @_;\n\n    my $drive_name = extract_param($params, 'drive_name');\n    my $format = extract_param($params, 'format');\n    if ($drive_name && !(PVE::QemuServer::is_valid_drivename($drive_name))) {\n        die \"invalid drive name: $drive_name\\n\";\n    }\n\n    # get target format, target image's path, and whether it's possible to sparseinit\n    my $storecfg = PVE::Storage::config();\n    my $dst_format =\n        PVE::QemuServer::resolve_dst_disk_format($storecfg, $storage_id, undef, $format);\n    warn \"format '$format' is not supported by the target storage - using '$dst_format' instead\\n\"\n        if $format && $format ne $dst_format;\n\n    my $dst_volid = PVE::Storage::vdisk_alloc(\n        $storecfg,\n        $storage_id,\n        $vmid,\n        $dst_format,\n        undef,\n        $src_size / 1024,\n    );\n\n    my $zeroinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $dst_volid);\n\n    my $create_drive = sub {\n        my $vm_conf = PVE::QemuConfig->load_config($vmid);\n        if (!$params->{skiplock}) {\n            PVE::QemuConfig->check_lock($vm_conf);\n        }\n\n        if ($drive_name) {\n            # should never happen as setting $drive_name is not exposed to public interface\n            die \"cowardly refusing to overwrite existing entry: $drive_name\\n\"\n                if $vm_conf->{$drive_name};\n\n            my $modified = {}; # record what $option we modify\n            $modified->{$drive_name} = 1;\n            $vm_conf->{pending}->{$drive_name} = $dst_volid;\n            PVE::QemuConfig->write_config($vmid, $vm_conf);\n\n            my $running = PVE::QemuServer::check_running($vmid);\n            if ($running) {\n                my $errors = {};\n                PVE::QemuServer::vmconfig_hotplug_pending(\n                    $vmid,\n                    $vm_conf,\n                    $storecfg,\n                    $modified,\n                    $errors,\n                );\n                warn \"hotplugging imported disk '$_' failed: $errors->{$_}\\n\" for keys %$errors;\n            } else {\n                PVE::QemuServer::vmconfig_apply_pending($vmid, $vm_conf, $storecfg);\n            }\n        } else {\n            $drive_name = PVE::QemuConfig->add_unused_volume($vm_conf, $dst_volid);\n            PVE::QemuConfig->write_config($vmid, $vm_conf);\n        }\n    };\n\n    eval {\n        # trap interrupts so we have a chance to clean up\n        local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n            local $SIG{PIPE} = sub { die \"interrupted by signal $!\\n\"; };\n\n        PVE::Storage::activate_volumes($storecfg, [$dst_volid]);\n        PVE::QemuServer::QemuImage::convert(\n            $src_path,\n            $dst_volid,\n            $src_size,\n            { 'is-zero-initialized' => $zeroinit },\n        );\n        PVE::Storage::deactivate_volumes($storecfg, [$dst_volid]);\n        PVE::QemuConfig->lock_config($vmid, $create_drive) if !$params->{'skip-config-update'};\n    };\n    if (my $err = $@) {\n        eval { PVE::Storage::vdisk_free($storecfg, $dst_volid) };\n        warn \"cleanup of $dst_volid failed: $@\\n\" if $@;\n        die $err;\n    }\n\n    return ($drive_name, $dst_volid);\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Machine.pm",
    "content": "package PVE::QemuServer::Machine;\n\nuse strict;\nuse warnings;\n\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::MetaInfo;\nuse PVE::QemuServer::Monitor;\nuse PVE::JSONSchema qw(get_standard_option parse_property_string print_property_string);\n\n# The PVE machine versions allow rolling out (incompatibel) changes to the hardware layout and/or\n# the QEMU command of a VM without requiring a newer QEMU upstream machine version.\n#\n# To use this find the newest available QEMU machine version, add and entry in this hash if it does\n# not already exists and then bump the higherst version, or introduce a new one starting at 1, as\n# the upstream version is counted as having a PVE revision of 0.\n# Additionally you must describe in short what the basic changes done with such a new PVE machine\n# revision in the respective subhash, use the full version including the pve one as key there.\n#\n# NOTE: Do not overuse this. one or two changes per upstream machine can be fine, if needed. But\n# most of the time it's better to batch more together and if there is no time pressure then wait a\n# few weeks/months until the next QEMU machine revision is ready. As it will get confusing otherwise\n# and we lazily use some simple ascii sort when processing these, so more than 10 per entry require\n# changes but should be avoided in the first place.\n# TODO: add basic test to ensure the keys are correct and there's a change entry for each version.\nour $PVE_MACHINE_VERSION = {\n    '4.1' => {\n        highest => 2,\n        revisions => {\n            '+pve1' => 'Introduction of pveX versioning, no changes.',\n            '+pve2' => 'Increases the number of SCSI drives supported.',\n        },\n    },\n    '9.2' => {\n        highest => 1,\n        revisions => {\n            '+pve1' => 'Disables S3/S4 power states by default.',\n        },\n    },\n    '10.0' => {\n        highest => 1,\n        revisions => {\n            '+pve1' => 'Set host_mtu vNIC option even with default value for migration compat.',\n        },\n    },\n};\n\nmy $machine_fmt = {\n    type => {\n        default_key => 1,\n        description => \"Specifies the QEMU machine type.\",\n        type => 'string',\n        pattern =>\n            '(pc|pc(-i440fx)?-\\d+(\\.\\d+)+(\\+pve\\d+)?(\\.pxe)?|q35|pc-q35-\\d+(\\.\\d+)+(\\+pve\\d+)?(\\.pxe)?|virt(?:-\\d+(\\.\\d+)+)?(\\+pve\\d+)?)',\n        maxLength => 40,\n        format_description => 'machine type',\n        optional => 1,\n    },\n    viommu => {\n        type => 'string',\n        description =>\n            \"Enable and set guest vIOMMU variant (Intel vIOMMU needs q35 to be set as\"\n            . \" machine type).\",\n        enum => ['intel', 'virtio'],\n        optional => 1,\n    },\n    'aw-bits' => {\n        type => 'number',\n        description => \"Specifies the vIOMMU address space bit width.\",\n        verbose_description => \"Specifies the vIOMMU address space bit width.\\n\\n\"\n            . \"Intel vIOMMU supports a bit width of either 39 or 48 bits and\"\n            . \" VirtIO vIOMMU supports any bit width between 32 and 64 bits.\",\n        minimum => 32,\n        maximum => 64,\n        optional => 1,\n    },\n    'enable-s3' => {\n        type => 'boolean',\n        description =>\n            \"Enables S3 power state. Defaults to false beginning with machine types 9.2+pve1, true before.\",\n        optional => 1,\n    },\n    'enable-s4' => {\n        type => 'boolean',\n        description =>\n            \"Enables S4 power state. Defaults to false beginning with machine types 9.2+pve1, true before.\",\n        optional => 1,\n    },\n};\n\nPVE::JSONSchema::register_format('pve-qemu-machine-fmt', $machine_fmt);\n\nPVE::JSONSchema::register_standard_option(\n    'pve-qemu-machine',\n    {\n        description => \"Specify the QEMU machine.\",\n        type => 'string',\n        optional => 1,\n        format => PVE::JSONSchema::get_format('pve-qemu-machine-fmt'),\n    },\n);\n\nsub parse_machine {\n    my ($value) = @_;\n\n    return if !$value;\n\n    my $res = parse_property_string($machine_fmt, $value);\n    return $res;\n}\n\nsub print_machine {\n    my ($machine_conf) = @_;\n    return print_property_string($machine_conf, $machine_fmt);\n}\n\nmy $default_machines = {\n    x86_64 => 'pc',\n    aarch64 => 'virt',\n};\n\nsub default_machine_for_arch {\n    my ($arch) = @_;\n\n    my $machine = $default_machines->{$arch} or die \"unsupported architecture '$arch'\\n\";\n    return $machine;\n}\n\nsub assert_valid_machine_property {\n    my ($machine_conf) = @_;\n    if ($machine_conf->{viommu} && $machine_conf->{viommu} eq \"intel\") {\n        my $q35 = $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0;\n        die \"to use Intel vIOMMU please set the machine type to q35\\n\" if !$q35;\n\n        die \"Intel vIOMMU supports only 39 or 48 bits as address width\\n\"\n            if $machine_conf->{'aw-bits'}\n            && $machine_conf->{'aw-bits'} != 39\n            && $machine_conf->{'aw-bits'} != 48;\n    }\n\n    die \"cannot set aw-bits if no vIOMMU is configured\\n\"\n        if $machine_conf->{'aw-bits'} && !$machine_conf->{viommu};\n}\n\n=head3 machine_base_type\n\n    my $base_type = machine_base_type($machine_type);\n\nReturns the base type of the machine, currently either C<i440fx>, C<q35> or C<virt>. A value must be\npassed in. Dies if the machine type cannot be determined, but should not happen if it is valid for\nthe C<$machine_fmt> schema.\n\n=cut\n\nmy sub machine_base_type {\n    my ($machine_type) = @_;\n\n    die \"unable to determine machine base type - no value\\n\" if !$machine_type;\n\n    return 'q35' if $machine_type =~ m/q35/;\n    return 'i440fx' if $machine_type =~ m/^pc/;\n    return 'virt' if $machine_type =~ m/^virt/;\n\n    die \"unable to determine machine base type '$machine_type'\\n\";\n}\n\nsub machine_type_is_q35 {\n    my ($conf) = @_;\n\n    my $machine_conf = parse_machine($conf->{machine});\n    return 0 if !$machine_conf || !$machine_conf->{type};\n    return machine_base_type($machine_conf->{type}) eq 'q35' ? 1 : 0;\n}\n\n# When you need to check a new flag, extend here and the POD for machine_supports_flag().\nmy $supported_machine_flags = {\n    i440fx => {\n        hpet => 1,\n    },\n    q35 => {\n        hpet => 1,\n    },\n    virt => {},\n};\n\n=head3 machine_supports_flag\n\n    if (machine_supports_flag($machine_type, $flag)) {\n        push $machine_flags->@*, $flag;\n    }\n\nCheck whether the machine type C<$machine_type> supports the machine flag C<$flag>. Both arguments\nmust have a value. Flags which can be checked currently: C<hpet>.\n\n=cut\n\nsub machine_supports_flag {\n    my ($machine_type, $flag) = @_;\n\n    die \"cannot check machine flag support - no machine type provided\\n\" if !$machine_type;\n    die \"cannot check machine flag support - no flag provided\\n\" if !$flag;\n\n    my $base_type = machine_base_type($machine_type);\n    return $supported_machine_flags->{$base_type}->{$flag};\n}\n\n# In list context, also returns whether the current machine is deprecated or not.\nsub current_from_query_machines {\n    my ($machines) = @_;\n\n    my ($current, $default);\n    for my $machine ($machines->@*) {\n        $default = $machine->{name} if $machine->{'is-default'};\n\n        if ($machine->{'is-current'}) {\n            $current = $machine->{name};\n            # pve-version only exists for the current machine\n            $current .= \"+$machine->{'pve-version'}\" if $machine->{'pve-version'};\n            return wantarray ? ($current, $machine->{deprecated} ? 1 : 0) : $current;\n        }\n    }\n\n    # fallback to the default machine if current is not supported by qemu - assume never deprecated\n    my $fallback = $default || 'pc';\n    return wantarray ? ($fallback, 0) : $fallback;\n}\n\n# This only works if VM is running.\n# In list context, also returns whether the current machine is deprecated or not.\nsub get_current_qemu_machine {\n    my ($vmid) = @_;\n\n    my $res = PVE::QemuServer::Monitor::mon_cmd($vmid, 'query-machines');\n\n    return current_from_query_machines($res);\n}\n\n=head3 extract_version_parts\n\n    my ($major, $minor, $pve) = extract_version_parts($machine_type);\n\nReturns the major, minor and pve versions from the given C<$machine_type> string. Returns nothing if\nthe string did not contain any version or if parsing failed.\n\n=cut\n\nmy sub extract_version_parts {\n    my ($machine_type) = @_;\n\n    if ($machine_type =~\n        m/^(?:pc(?:-i440fx|-q35)?|virt)-(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:\\+pve(\\d+))?(?:\\.pxe)?/\n    ) {\n        return ($1, $2, $4);\n    }\n    return;\n}\n\n# returns a string with major.minor+pve<VERSION>, patch version-part is ignored\n# as it's seldom resembling a real QEMU machine type, so it would be '0' 99% of\n# the time anyway.. This explicitly separates pveversion from the machine.\nsub extract_version {\n    my ($machine_type, $kvmversion) = @_;\n\n    my ($major, $minor, $pve);\n    ($major, $minor, $pve) = extract_version_parts($machine_type) if defined($machine_type);\n    if (defined($major) && defined($minor)) {\n        my $versionstr = \"${major}.${minor}\";\n        $versionstr .= \"+pve${pve}\" if $pve;\n        return $versionstr;\n    } elsif (defined($kvmversion)) {\n        if ($kvmversion =~ m/^(\\d+)\\.(\\d+)/) {\n            my $pvever = get_pve_version($kvmversion);\n            return \"$1.$2+pve$pvever\";\n        }\n    }\n\n    return;\n}\n\n=head3 machine_version_cmp\n\n    sort { machine_version_cmp($a, $b) } @machine_types\n\nComparision function for sorting machine types by version.\n\n=cut\n\nsub machine_version_cmp {\n    my ($machine_type_a, $machine_type_b) = @_;\n\n    my ($major_a, $minor_a, $pve_a) = extract_version_parts($machine_type_a);\n    my ($major_b, $minor_b, $pve_b) = extract_version_parts($machine_type_b);\n\n    return PVE::QemuServer::Helpers::version_cmp(\n        $major_a,\n        $major_b,\n        $minor_a,\n        $minor_b,\n        $pve_a,\n        $pve_b,\n    );\n}\n\nsub is_machine_version_at_least {\n    my ($machine_type, $major, $minor, $pve) = @_;\n\n    return PVE::QemuServer::Helpers::min_version(extract_version($machine_type), $major, $minor,\n        $pve);\n}\n\nsub get_machine_pve_revisions {\n    my ($machine_version_str) = @_;\n\n    if ($machine_version_str =~ m/^(\\d+\\.\\d+)/) {\n        return $PVE_MACHINE_VERSION->{$1};\n    }\n\n    die \"internal error: cannot get pve version for invalid string '$machine_version_str'\";\n}\n\nsub get_pve_version {\n    my ($verstr) = @_;\n\n    if (my $pve_machine = get_machine_pve_revisions($verstr)) {\n        return $pve_machine->{highest}\n            || die \"internal error - machine version '$verstr' missing 'highest'\";\n    }\n\n    return 0;\n}\n\nsub can_run_pve_machine_version {\n    my ($machine_version, $kvmversion) = @_;\n\n    $machine_version =~ m/^(\\d+)\\.(\\d+)(?:\\+pve(\\d+))?(?:\\.pxe)?$/;\n    my $major = $1;\n    my $minor = $2;\n    my $pvever = $3;\n\n    $kvmversion =~ m/(\\d+)\\.(\\d+)/;\n    return 0 if PVE::QemuServer::Helpers::version_cmp($1, $major, $2, $minor) < 0;\n\n    # if $pvever is missing or 0, we definitely support it as long as we didn't\n    # fail the QEMU version check above\n    return 1 if !$pvever;\n\n    my $max_supported = get_pve_version(\"$major.$minor\");\n    return 1 if $max_supported >= $pvever;\n\n    return 0;\n}\n\nsub qemu_machine_pxe {\n    my ($vmid, $conf) = @_;\n\n    my $machine = get_current_qemu_machine($vmid);\n\n    my $machine_conf = parse_machine($conf->{machine});\n    if ($machine_conf->{type} && $machine_conf->{type} =~ m/\\.pxe$/) {\n        $machine .= '.pxe';\n    }\n\n    return $machine;\n}\n\nsub latest_installed_machine_version {\n    my ($kvmversion) = @_;\n\n    $kvmversion = PVE::QemuServer::Helpers::kvm_user_version() if !defined($kvmversion);\n\n    my ($version) = ($kvmversion =~ m/^(\\d+\\.\\d+)/);\n\n    my $pvever = get_pve_version($version);\n    $version .= \"+pve$pvever\" if $pvever > 0;\n\n    return $version;\n}\n\nsub windows_get_pinned_machine_version {\n    my ($machine, $base_version, $kvmversion) = @_;\n\n    die \"internal error - no machine provided\" if !$machine;\n\n    my $pin_version = $base_version;\n    if (!defined($base_version) || !can_run_pve_machine_version($base_version, $kvmversion)) {\n        $pin_version = latest_installed_machine_version($kvmversion);\n    }\n    if ($machine eq 'pc') {\n        $machine = \"pc-i440fx-$pin_version\";\n    } elsif ($machine eq 'q35') {\n        $machine = \"pc-q35-$pin_version\";\n    } elsif ($machine eq 'virt') {\n        $machine = \"virt-$pin_version\";\n    } else {\n        warn \"unknown machine type '$machine', not touching that!\\n\";\n    }\n\n    return $machine;\n}\n\nsub get_vm_machine {\n    my ($conf, $forcemachine) = @_;\n\n    my $machine_conf = parse_machine($conf->{machine});\n    my $machine = $forcemachine || $machine_conf->{type};\n\n    if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {\n        my $kvmversion = PVE::QemuServer::Helpers::kvm_user_version();\n        my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n        $machine ||= default_machine_for_arch($arch);\n\n        # we must pin Windows VMs without a specific version and no meta info about creation QEMU to\n        # 5.1, as 5.2 fixed a bug in ACPI layout which confuses windows quite a bit and may result\n        # in various regressions..\n        # see: https://lists.gnu.org/archive/html/qemu-devel/2021-02/msg08484.html\n        # Starting from QEMU 9.1, pin to the creation version instead. Support for 5.1 is expected\n        # to drop with QEMU 11.1 and it would still be good to handle Windows VMs that do not have\n        # an explicit machine version for whatever reason.\n        if (PVE::QemuServer::Helpers::windows_version($conf->{ostype})) {\n            my $base_version = '5.1';\n            # TODO PVE 10 - die early if there is a Windows VM both without explicit machine version\n            # and without meta info.\n            if (my $meta = PVE::QemuServer::MetaInfo::parse_meta_info($conf->{meta})) {\n                if (PVE::QemuServer::Helpers::min_version($meta->{'creation-qemu'}, 9, 1)) {\n                    # need only major.minor\n                    ($base_version) = ($meta->{'creation-qemu'} =~ m/^(\\d+.\\d+)/);\n                }\n            }\n            $machine = windows_get_pinned_machine_version($machine, $base_version, $kvmversion);\n        } else {\n            my $pvever = get_pve_version($kvmversion);\n            $machine .= \"+pve$pvever\";\n        }\n    }\n\n    if ($machine !~ m/\\+pve\\d+?(?:\\.pxe)?$/) {\n        my $is_pxe = $machine =~ m/^(.*?)\\.pxe$/;\n        $machine = $1 if $is_pxe;\n\n        # for version-pinned machines that do not include a pve-version (e.g.\n        # pc-q35-4.1), we assume 0 to keep them stable in case we bump\n        $machine .= '+pve0';\n\n        $machine .= '.pxe' if $is_pxe;\n    }\n\n    return $machine;\n}\n\nsub check_and_pin_machine_string {\n    my ($machine_string, $ostype, $arch) = @_;\n\n    my $machine_conf = parse_machine($machine_string);\n    my $machine = $machine_conf->{type};\n    if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {\n        # always pin Windows' machine version on create, they get confused too easily\n        if (PVE::QemuServer::Helpers::windows_version($ostype)) {\n            $machine = default_machine_for_arch($arch) if !$machine;\n            $machine_conf->{type} = windows_get_pinned_machine_version($machine);\n            print \"pinning machine type to '$machine_conf->{type}' for Windows guest OS\\n\";\n        }\n    }\n\n    assert_valid_machine_property($machine_conf);\n    return print_machine($machine_conf);\n}\n\n# disable s3/s4 by default for 9.2+pve1 machine types\n# returns an arrayref of cmdline options for qemu or undef\nsub get_power_state_flags {\n    my ($machine_conf, $arch, $version_guard) = @_;\n\n    my $machine = $machine_conf->{type} || default_machine_for_arch($arch);\n\n    if ($machine =~ /^virt/) {\n        return; # virt machines are normally ARM64 ones which have no concept of s3/s4\n    }\n\n    my $object = ($machine =~ m/q35/) ? \"ICH9-LPC\" : \"PIIX4_PM\";\n\n    my $default = 1;\n    if ($version_guard->(9, 2, 1)) {\n        $default = 0;\n    }\n\n    my $s3 = $machine_conf->{'enable-s3'} // $default;\n    my $s4 = $machine_conf->{'enable-s4'} // $default;\n\n    my $options = [];\n\n    # they're enabled by default in QEMU, so only add the flags to disable them\n    if (!$s3) {\n        push $options->@*, '-global', \"${object}.disable_s3=1\";\n    }\n    if (!$s4) {\n        push $options->@*, '-global', \"${object}.disable_s4=1\";\n    }\n\n    if (scalar($options->@*)) {\n        return $options;\n    }\n\n    return;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\nSOURCES=Agent.pm\t\\\n\tBlockdev.pm\t\\\n\tBlockJob.pm\t\\\n\tCfg2Cmd.pm\t\\\n\tCGroup.pm\t\\\n\tCloudinit.pm\t\\\n\tCPUConfig.pm\t\\\n\tDBusVMState.pm\t\\\n\tDrive.pm\t\\\n\tDriveDevice.pm\t\\\n\tHelpers.pm\t\\\n\tImportDisk.pm\t\\\n\tMachine.pm\t\\\n\tMemory.pm\t\\\n\tMetaInfo.pm\t\\\n\tMonitor.pm\t\\\n\tNetwork.pm\t\\\n\tOVMF.pm\t\t\\\n\tPCI.pm\t\t\\\n\tQemuImage.pm\t\\\n\tQMPHelpers.pm\t\\\n\tQSD.pm\t\t\\\n\tRNG.pm\t\t\\\n\tRunState.pm\t\\\n\tStateFile.pm\t\\\n\tUSB.pm\t\t\\\n\tVirtiofs.pm\t\\\n\tVolumeChain.pm\n\n.PHONY: install\ninstall: $(SOURCES)\n\tfor i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/QemuServer/$$i; done\n\t$(MAKE) -C Cfg2Cmd install\n"
  },
  {
    "path": "src/PVE/QemuServer/Memory.pm",
    "content": "package PVE::QemuServer::Memory;\n\nuse strict;\nuse warnings;\n\nuse PVE::JSONSchema qw(parse_property_string);\nuse PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach);\nuse PVE::Exception qw(raise raise_param_exc);\n\nuse PVE::QemuServer::Helpers qw(parse_number_sets);\nuse PVE::QemuServer::Monitor qw(mon_cmd);\nuse PVE::QemuServer::QMPHelpers qw(qemu_devicedel qemu_objectdel);\n\nuse base qw(Exporter);\n\nour @EXPORT_OK = qw(\n    get_current_memory\n);\n\nour $MAX_NUMA = 8;\n\nmy $numa_fmt = {\n    cpus => {\n        type => \"string\",\n        pattern => qr/\\d+(?:-\\d+)?(?:;\\d+(?:-\\d+)?)*/,\n        description => \"CPUs accessing this NUMA node.\",\n        format_description => \"id[-id];...\",\n    },\n    memory => {\n        type => \"number\",\n        description => \"Amount of memory this NUMA node provides.\",\n        optional => 1,\n    },\n    hostnodes => {\n        type => \"string\",\n        pattern => qr/\\d+(?:-\\d+)?(?:;\\d+(?:-\\d+)?)*/,\n        description => \"Host NUMA nodes to use.\",\n        format_description => \"id[-id];...\",\n        optional => 1,\n    },\n    policy => {\n        type => 'string',\n        enum => [qw(preferred bind interleave)],\n        description => \"NUMA allocation policy.\",\n        optional => 1,\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-numanode', $numa_fmt);\nour $numadesc = {\n    optional => 1,\n    type => 'string',\n    format => $numa_fmt,\n    description => \"NUMA topology.\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-numanode\", $numadesc);\n\nsub parse_numa {\n    my ($data) = @_;\n\n    my $res = parse_property_string($numa_fmt, $data);\n    $res->{cpus} = parse_number_sets($res->{cpus}) if defined($res->{cpus});\n    $res->{hostnodes} = parse_number_sets($res->{hostnodes}) if defined($res->{hostnodes});\n    return $res;\n}\n\nmy $STATICMEM = 1024;\n\nour $memory_fmt = {\n    current => {\n        description =>\n            \"Current amount of online RAM for the VM in MiB. This is the maximum available memory when\"\n            . \" you use the balloon device.\",\n        type => 'integer',\n        default_key => 1,\n        minimum => 16,\n        default => 512,\n    },\n};\n\nsub print_memory {\n    my $memory = shift;\n\n    return PVE::JSONSchema::print_property_string($memory, $memory_fmt);\n}\n\nsub parse_memory {\n    my ($value) = @_;\n\n    return { current => $memory_fmt->{current}->{default} } if !defined($value);\n\n    my $res = PVE::JSONSchema::parse_property_string($memory_fmt, $value);\n\n    return $res;\n}\n\nmy sub get_max_mem {\n    my ($conf) = @_;\n\n    my $cpu = {};\n    if (my $cpu_prop_str = $conf->{cpu}) {\n        $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)\n            or die \"Cannot parse cpu description: $cpu_prop_str\\n\";\n    }\n    my $bits;\n    if (my $phys_bits = $cpu->{'phys-bits'}) {\n        if ($phys_bits eq 'host') {\n            $bits = PVE::QemuServer::Helpers::get_host_phys_address_bits();\n        } elsif ($phys_bits =~ /^(\\d+)$/) {\n            $bits = int($phys_bits);\n        }\n    }\n\n    if (!defined($bits)) {\n        # fixme: what fallback?\n        my $host_bits = PVE::QemuServer::Helpers::get_host_phys_address_bits() // 36;\n        if ($cpu->{cputype} && $cpu->{cputype} =~ /^(host|max)$/) {\n            $bits = $host_bits;\n        } else {\n            $bits = $host_bits > 40 ? 40 : $host_bits; # take the smaller one\n        }\n    }\n\n    $bits = $bits & ~1; # round down to nearest even as limit is lower with odd bit sizes\n\n    # heuristic: remove 20 bits to get MB and half that as QEMU needs some overhead\n    my $bits_to_max_mem = int(1 << ($bits - 21));\n\n    return $bits_to_max_mem > 4 * 1024 * 1024 ? 4 * 1024 * 1024 : $bits_to_max_mem;\n}\n\nsub get_current_memory {\n    my ($value) = @_;\n\n    my $memory = parse_memory($value);\n    return $memory->{current};\n}\n\nsub get_numa_node_list {\n    my ($conf) = @_;\n    my @numa_map;\n    for (my $i = 0; $i < $MAX_NUMA; $i++) {\n        my $entry = $conf->{\"numa$i\"} or next;\n        my $numa = parse_numa($entry) or next;\n        push @numa_map, $i;\n    }\n    return @numa_map if @numa_map;\n    my $sockets = $conf->{sockets} || 1;\n    return (0 .. ($sockets - 1));\n}\n\nsub host_numanode_exists {\n    my ($id) = @_;\n\n    return -d \"/sys/devices/system/node/node$id/\";\n}\n\n# only valid when numa nodes map to a single host node\nsub get_numa_guest_to_host_map {\n    my ($conf) = @_;\n    my $map = {};\n    for (my $i = 0; $i < $MAX_NUMA; $i++) {\n        my $entry = $conf->{\"numa$i\"} or next;\n        my $numa = parse_numa($entry) or next;\n        $map->{$i} = print_numa_hostnodes($numa->{hostnodes});\n    }\n    return $map if %$map;\n    my $sockets = $conf->{sockets} || 1;\n    return { map { $_ => $_ } (0 .. ($sockets - 1)) };\n}\n\nsub foreach_dimm {\n    my ($conf, $vmid, $memory, $static_memory, $func) = @_;\n\n    my $dimm_id = 0;\n    my $current_size = $static_memory;\n    my $dimm_size = 0;\n\n    if ($conf->{hugepages} && $conf->{hugepages} == 1024) {\n        $dimm_size = 1024;\n    } else {\n        $dimm_size = 512;\n    }\n\n    return if $current_size == $memory;\n\n    my @numa_map = get_numa_node_list($conf);\n\n    for (my $j = 0; $j < 8; $j++) {\n        for (my $i = 0; $i < 32; $i++) {\n            my $name = \"dimm${dimm_id}\";\n            $dimm_id++;\n            my $numanode = $numa_map[$i % @numa_map];\n            $current_size += $dimm_size;\n            &$func($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory);\n            return $current_size if $current_size >= $memory;\n        }\n        $dimm_size *= 2;\n    }\n}\n\nsub qemu_memory_hotplug {\n    my ($vmid, $conf, $value) = @_;\n\n    return $value if !PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    my $oldmem = parse_memory($conf->{memory});\n    my $newmem = parse_memory($value);\n\n    return $value if $newmem->{current} == $oldmem->{current};\n\n    my $memory = $oldmem->{current};\n    $value = $newmem->{current};\n\n    my $sockets = $conf->{sockets} || 1;\n    my $static_memory = $STATICMEM;\n    $static_memory = $static_memory * $sockets\n        if ($conf->{hugepages} && $conf->{hugepages} == 1024);\n\n    die \"memory can't be lower than $static_memory MB\" if $value < $static_memory;\n    my $MAX_MEM = get_max_mem($conf);\n    die \"you cannot add more memory than max mem $MAX_MEM MB!\\n\" if $value > $MAX_MEM;\n\n    if ($value > $memory) {\n\n        my $numa_hostmap;\n\n        foreach_dimm(\n            $conf,\n            $vmid,\n            $value,\n            $static_memory,\n            sub {\n                my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;\n\n                return if $current_size <= get_current_memory($conf->{memory});\n\n                if ($conf->{hugepages}) {\n                    $numa_hostmap = get_numa_guest_to_host_map($conf) if !$numa_hostmap;\n\n                    my $hugepages_size = hugepages_size($conf, $dimm_size);\n                    my $path = hugepages_mount_path($hugepages_size);\n                    my $host_numanode = $numa_hostmap->{$numanode};\n                    my $hugepages_topology->{$hugepages_size}->{$host_numanode} =\n                        hugepages_nr($dimm_size, $hugepages_size);\n\n                    my $code = sub {\n                        my $hugepages_host_topology = hugepages_host_topology();\n                        hugepages_allocate($hugepages_topology, $hugepages_host_topology);\n\n                        eval {\n                            mon_cmd(\n                                $vmid, \"object-add\",\n                                'qom-type' => \"memory-backend-file\",\n                                id => \"mem-$name\",\n                                size => int($dimm_size * 1024 * 1024),\n                                'mem-path' => $path,\n                                share => JSON::true,\n                                prealloc => JSON::true,\n                            );\n                        };\n                        if (my $err = $@) {\n                            hugepages_reset($hugepages_host_topology);\n                            die $err;\n                        }\n\n                        hugepages_pre_deallocate($hugepages_topology);\n                    };\n                    eval { hugepages_update_locked($code); };\n\n                } else {\n                    eval {\n                        mon_cmd(\n                            $vmid, \"object-add\",\n                            'qom-type' => \"memory-backend-ram\",\n                            id => \"mem-$name\",\n                            size => int($dimm_size * 1024 * 1024),\n                        );\n                    };\n                }\n\n                if (my $err = $@) {\n                    eval { qemu_objectdel($vmid, \"mem-$name\"); };\n                    die $err;\n                }\n\n                eval {\n                    mon_cmd(\n                        $vmid, \"device_add\",\n                        driver => \"pc-dimm\",\n                        id => \"$name\",\n                        memdev => \"mem-$name\",\n                        node => $numanode,\n                    );\n                };\n                if (my $err = $@) {\n                    eval { qemu_objectdel($vmid, \"mem-$name\"); };\n                    die $err;\n                }\n                # update conf after each successful module hotplug\n                $newmem->{current} = $current_size;\n                $conf->{memory} = print_memory($newmem);\n                PVE::QemuConfig->write_config($vmid, $conf);\n            },\n        );\n\n    } else {\n\n        my $dimms = qemu_memdevices_list($vmid, 'dimm');\n\n        my $current_size = $memory;\n        for my $name (sort { ($b =~ /^dimm(\\d+)$/)[0] <=> ($a =~ /^dimm(\\d+)$/)[0] } keys %$dimms) {\n\n            my $dimm_size = $dimms->{$name}->{size} / 1024 / 1024;\n\n            last if $current_size <= $value;\n\n            print \"try to unplug memory dimm $name\\n\";\n\n            my $retry = 0;\n            while (1) {\n                eval { qemu_devicedel($vmid, $name) };\n                sleep 3;\n                my $dimm_list = qemu_memdevices_list($vmid, 'dimm');\n                last if !$dimm_list->{$name};\n                raise_param_exc({ $name => \"error unplug memory module\" }) if $retry > 5;\n                $retry++;\n            }\n            $current_size -= $dimm_size;\n            # update conf after each successful module unplug\n            $newmem->{current} = $current_size;\n            $conf->{memory} = print_memory($newmem);\n\n            eval { qemu_objectdel($vmid, \"mem-$name\"); };\n            PVE::QemuConfig->write_config($vmid, $conf);\n        }\n    }\n    return $conf->{memory};\n}\n\nsub qemu_memdevices_list {\n    my ($vmid, $type) = @_;\n\n    my $dimmarray = mon_cmd($vmid, \"query-memory-devices\");\n    my $dimms = {};\n\n    foreach my $dimm (@$dimmarray) {\n        next if $type && $dimm->{data}->{id} !~ /^$type(\\d+)$/;\n        $dimms->{ $dimm->{data}->{id} }->{id} = $dimm->{data}->{id};\n        $dimms->{ $dimm->{data}->{id} }->{node} = $dimm->{data}->{node};\n        $dimms->{ $dimm->{data}->{id} }->{addr} = $dimm->{data}->{addr};\n        $dimms->{ $dimm->{data}->{id} }->{size} = $dimm->{data}->{size};\n        $dimms->{ $dimm->{data}->{id} }->{slot} = $dimm->{data}->{slot};\n    }\n    return $dimms;\n}\n\nsub config {\n    my ($conf, $vmid, $sockets, $cores, $hotplug, $virtiofs_enabled, $cmd, $machine_flags) = @_;\n\n    my $memory = get_current_memory($conf->{memory});\n    my $static_memory = 0;\n\n    if ($hotplug) {\n        die \"NUMA needs to be enabled for memory hotplug\\n\" if !$conf->{numa};\n        my $MAX_MEM = get_max_mem($conf);\n        die \"Total memory is bigger than ${MAX_MEM}MB\\n\" if $memory > $MAX_MEM;\n\n        for (my $i = 0; $i < $MAX_NUMA; $i++) {\n            die \"cannot enable memory hotplugging with custom NUMA topology\\n\"\n                if $conf->{\"numa$i\"};\n        }\n\n        my $sockets = $conf->{sockets} || 1;\n\n        $static_memory = $STATICMEM;\n        $static_memory = $static_memory * $sockets\n            if ($conf->{hugepages} && $conf->{hugepages} == 1024);\n\n        die \"minimum memory must be ${static_memory}MB\\n\" if ($memory < $static_memory);\n        push @$cmd, '-m', \"size=${static_memory},slots=255,maxmem=${MAX_MEM}M\";\n\n    } else {\n\n        $static_memory = $memory;\n        push @$cmd, '-m', $static_memory;\n    }\n\n    die \"numa needs to be enabled to use hugepages\" if $conf->{hugepages} && !$conf->{numa};\n\n    die \"Memory hotplug does not work in combination with virtio-fs.\\n\"\n        if $hotplug && $virtiofs_enabled;\n\n    if ($conf->{numa}) {\n\n        my $numa_totalmemory = undef;\n        for (my $i = 0; $i < $MAX_NUMA; $i++) {\n            next if !$conf->{\"numa$i\"};\n            my $numa = parse_numa($conf->{\"numa$i\"});\n            next if !$numa;\n            # memory\n            die \"missing NUMA node$i memory value\\n\" if !$numa->{memory};\n            my $numa_memory = $numa->{memory};\n            $numa_totalmemory += $numa_memory;\n\n            my $memdev = $virtiofs_enabled ? \"virtiofs-mem$i\" : \"ram-node$i\";\n            my $mem_object = print_mem_object($conf, $memdev, $numa_memory);\n\n            # cpus\n            my $cpulists = $numa->{cpus};\n            die \"missing NUMA node$i cpus\\n\" if !defined($cpulists);\n            my $cpus = join(\n                ',cpus=',\n                map {\n                    my ($start, $end) = @$_;\n                    defined($end) ? \"$start-$end\" : $start\n                } @$cpulists,\n            );\n\n            # hostnodes\n            my $hostnodelists = $numa->{hostnodes};\n            if (defined($hostnodelists)) {\n\n                my $hostnodes = print_numa_hostnodes($hostnodelists);\n\n                # policy\n                my $policy = $numa->{policy};\n                die \"you need to define a policy for hostnode $hostnodes\\n\" if !$policy;\n                $mem_object .= \",host-nodes=$hostnodes,policy=$policy\";\n            } else {\n                die \"numa hostnodes need to be defined to use hugepages\" if $conf->{hugepages};\n            }\n\n            push @$cmd, '-object', $mem_object;\n            push @$cmd, '-numa', \"node,nodeid=$i,cpus=$cpus,memdev=$memdev\";\n        }\n\n        die \"total memory for NUMA nodes must be equal to vm static memory\\n\"\n            if $numa_totalmemory && $numa_totalmemory != $static_memory;\n\n        #if no custom tology, we split memory and cores across numa nodes\n        if (!$numa_totalmemory) {\n            my $numa_memory = ($static_memory / $sockets);\n\n            for (my $i = 0; $i < $sockets; $i++) {\n                die \"host NUMA node$i doesn't exist\\n\"\n                    if !host_numanode_exists($i) && $conf->{hugepages};\n\n                my $cpus = ($cores * $i);\n                $cpus .= \"-\" . ($cpus + $cores - 1) if $cores > 1;\n\n                my $memdev = $virtiofs_enabled ? \"virtiofs-mem$i\" : \"ram-node$i\";\n                my $mem_object = print_mem_object($conf, $memdev, $numa_memory);\n                push @$cmd, '-object', $mem_object;\n                push @$cmd, '-numa', \"node,nodeid=$i,cpus=$cpus,memdev=$memdev\";\n            }\n        }\n    } elsif ($virtiofs_enabled) {\n        # kvm: '-machine memory-backend' and '-numa memdev' properties are mutually exclusive\n        push @$cmd, '-object',\n            'memory-backend-memfd,id=virtiofs-mem' . \",size=$conf->{memory}M,share=on\";\n        push @$machine_flags, 'memory-backend=virtiofs-mem';\n    }\n\n    if ($hotplug) {\n        foreach_dimm(\n            $conf,\n            $vmid,\n            $memory,\n            $static_memory,\n            sub {\n                my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;\n\n                my $mem_object = print_mem_object($conf, \"mem-$name\", $dimm_size);\n\n                push @$cmd, \"-object\", $mem_object;\n                push @$cmd, \"-device\", \"pc-dimm,id=$name,memdev=mem-$name,node=$numanode\";\n\n                die \"memory size ($memory) must be aligned to $dimm_size for hotplugging\\n\"\n                    if $current_size > $memory;\n            },\n        );\n    }\n}\n\nsub print_mem_object {\n    my ($conf, $id, $size) = @_;\n\n    if ($conf->{hugepages}) {\n\n        my $hugepages_size = hugepages_size($conf, $size);\n        my $path = hugepages_mount_path($hugepages_size);\n\n        return \"memory-backend-file,id=$id,size=${size}M,mem-path=$path,share=on,prealloc=yes\";\n    } elsif ($id =~ m/^virtiofs-mem/) {\n        return \"memory-backend-memfd,id=$id,size=${size}M,share=on\";\n    } else {\n        return \"memory-backend-ram,id=$id,size=${size}M\";\n    }\n\n}\n\nsub print_numa_hostnodes {\n    my ($hostnodelists) = @_;\n\n    my $hostnodes;\n    foreach my $hostnoderange (@$hostnodelists) {\n        my ($start, $end) = @$hostnoderange;\n        $hostnodes .= ',' if $hostnodes;\n        $hostnodes .= $start;\n        $hostnodes .= \"-$end\" if defined($end);\n        $end //= $start;\n        for (my $i = $start; $i <= $end; ++$i) {\n            die \"host NUMA node$i doesn't exist\\n\" if !host_numanode_exists($i);\n        }\n    }\n    return $hostnodes;\n}\n\nsub hugepages_mount {\n\n    my $mountdata = PVE::ProcFSTools::parse_proc_mounts();\n\n    foreach my $size (qw(2048 1048576)) {\n        next if (!-d \"/sys/kernel/mm/hugepages/hugepages-${size}kB\");\n\n        my $path = \"/run/hugepages/kvm/${size}kB\";\n\n        my $found = grep {\n            $_->[2] =~ /^hugetlbfs/\n                && $_->[1] eq $path\n        } @$mountdata;\n\n        if (!$found) {\n\n            File::Path::make_path($path) if (!-d $path);\n            my $cmd =\n                ['/bin/mount', '-t', 'hugetlbfs', '-o', \"pagesize=${size}k\", 'hugetlbfs', $path];\n            run_command($cmd, errmsg => \"hugepage mount error\");\n        }\n    }\n}\n\nsub hugepages_mount_path {\n    my ($size) = @_;\n\n    $size = $size * 1024;\n    return \"/run/hugepages/kvm/${size}kB\";\n\n}\n\nsub hugepages_nr {\n    my ($size, $hugepages_size) = @_;\n\n    return $size / $hugepages_size;\n}\n\nsub hugepages_chunk_size_supported {\n    my ($size) = @_;\n\n    return -d \"/sys/kernel/mm/hugepages/hugepages-\" . ($size * 1024) . \"kB\";\n}\n\nsub hugepages_size {\n    my ($conf, $size) = @_;\n    die \"hugepages option is not enabled\" if !$conf->{hugepages};\n    die \"memory size '$size' is not a positive even integer; cannot use for hugepages\\n\"\n        if $size <= 0 || $size & 1;\n\n    die \"your system doesn't support hugepages\\n\"\n        if !hugepages_chunk_size_supported(2) && !hugepages_chunk_size_supported(1024);\n\n    if ($conf->{hugepages} eq 'any') {\n\n        # try to use 1GB if available && memory size is matching\n        if (hugepages_chunk_size_supported(1024) && ($size & 1023) == 0) {\n            return 1024;\n        } elsif (hugepages_chunk_size_supported(2)) {\n            return 2;\n        } else {\n            die\n                \"host only supports 1024 GB hugepages, but requested size '$size' is not a multiple of 1024 MB\\n\";\n        }\n    } else {\n\n        my $hugepagesize = $conf->{hugepages};\n\n        if (!hugepages_chunk_size_supported($hugepagesize)) {\n            die \"your system doesn't support hugepages of $hugepagesize MB\\n\";\n        } elsif (($size % $hugepagesize) != 0) {\n            die\n                \"Memory size $size is not a multiple of the requested hugepages size $hugepagesize\\n\";\n        }\n\n        return $hugepagesize;\n    }\n}\n\nsub hugepages_topology {\n    my ($conf, $hotplug) = @_;\n\n    my $hugepages_topology = {};\n\n    return if !$conf->{numa};\n\n    my $memory = get_current_memory($conf->{memory});\n    my $static_memory = 0;\n    my $sockets = $conf->{sockets} || 1;\n    my $numa_custom_topology = undef;\n\n    if ($hotplug) {\n        $static_memory = $STATICMEM;\n        $static_memory = $static_memory * $sockets\n            if ($conf->{hugepages} && $conf->{hugepages} == 1024);\n    } else {\n        $static_memory = $memory;\n    }\n\n    #custom numa topology\n    for (my $i = 0; $i < $MAX_NUMA; $i++) {\n        next if !$conf->{\"numa$i\"};\n        my $numa = parse_numa($conf->{\"numa$i\"});\n        next if !$numa;\n\n        $numa_custom_topology = 1;\n        my $numa_memory = $numa->{memory};\n        my $hostnodelists = $numa->{hostnodes};\n        my $hostnodes = print_numa_hostnodes($hostnodelists);\n\n        die \"more than 1 hostnode value in numa node is not supported when hugepages are enabled\"\n            if $hostnodes !~ m/^(\\d)$/;\n        my $hugepages_size = hugepages_size($conf, $numa_memory);\n        $hugepages_topology->{$hugepages_size}->{$hostnodes} +=\n            hugepages_nr($numa_memory, $hugepages_size);\n\n    }\n\n    #if no custom numa tology, we split memory and cores across numa nodes\n    if (!$numa_custom_topology) {\n\n        my $numa_memory = ($static_memory / $sockets);\n\n        for (my $i = 0; $i < $sockets; $i++) {\n\n            my $hugepages_size = hugepages_size($conf, $numa_memory);\n            $hugepages_topology->{$hugepages_size}->{$i} +=\n                hugepages_nr($numa_memory, $hugepages_size);\n        }\n    }\n\n    if ($hotplug) {\n        my $numa_hostmap = get_numa_guest_to_host_map($conf);\n\n        foreach_dimm(\n            $conf,\n            undef,\n            $memory,\n            $static_memory,\n            sub {\n                my ($conf, undef, $name, $dimm_size, $numanode, $current_size, $memory) = @_;\n\n                $numanode = $numa_hostmap->{$numanode};\n\n                my $hugepages_size = hugepages_size($conf, $dimm_size);\n                $hugepages_topology->{$hugepages_size}->{$numanode} +=\n                    hugepages_nr($dimm_size, $hugepages_size);\n            },\n        );\n    }\n\n    return $hugepages_topology;\n}\n\nsub hugepages_host_topology {\n\n    #read host hugepages\n    my $hugepages_host_topology = {};\n\n    dir_glob_foreach(\n        \"/sys/devices/system/node/\",\n        'node(\\d+)',\n        sub {\n            my ($nodepath, $numanode) = @_;\n\n            dir_glob_foreach(\n                \"/sys/devices/system/node/$nodepath/hugepages/\",\n                'hugepages\\-(\\d+)kB',\n                sub {\n                    my ($hugepages_path, $hugepages_size) = @_;\n\n                    $hugepages_size = $hugepages_size / 1024;\n                    my $hugepages_nr = PVE::Tools::file_read_firstline(\n                        \"/sys/devices/system/node/$nodepath/hugepages/$hugepages_path/nr_hugepages\"\n                    );\n                    $hugepages_host_topology->{$hugepages_size}->{$numanode} = $hugepages_nr;\n                },\n            );\n        },\n    );\n\n    return $hugepages_host_topology;\n}\n\nsub hugepages_allocate {\n    my ($hugepages_topology, $hugepages_host_topology) = @_;\n\n    #allocate new hupages if needed\n    foreach my $size (sort keys %$hugepages_topology) {\n\n        my $nodes = $hugepages_topology->{$size};\n\n        foreach my $numanode (keys %$nodes) {\n\n            my $hugepages_size = $size * 1024;\n            my $hugepages_requested = $hugepages_topology->{$size}->{$numanode};\n            my $path =\n                \"/sys/devices/system/node/node${numanode}/hugepages/hugepages-${hugepages_size}kB/\";\n            my $hugepages_free = PVE::Tools::file_read_firstline($path . \"free_hugepages\");\n            my $hugepages_nr = PVE::Tools::file_read_firstline($path . \"nr_hugepages\");\n\n            if ($hugepages_requested > $hugepages_free) {\n                my $hugepages_needed = $hugepages_requested - $hugepages_free;\n                PVE::ProcFSTools::write_proc_entry(\n                    $path . \"nr_hugepages\",\n                    $hugepages_nr + $hugepages_needed,\n                );\n                #verify that is correctly allocated\n                $hugepages_free = PVE::Tools::file_read_firstline($path . \"free_hugepages\");\n                if ($hugepages_free < $hugepages_requested) {\n                    #rollback to initial host config\n                    hugepages_reset($hugepages_host_topology);\n                    die \"hugepage allocation failed\";\n                }\n            }\n\n        }\n    }\n\n}\n\nsub hugepages_default_nr_hugepages {\n    my ($size) = @_;\n\n    my $cmdline = PVE::Tools::file_read_firstline(\"/proc/cmdline\");\n    my $args = PVE::Tools::split_args($cmdline);\n\n    my $parsed_size = 2; # default is 2M\n\n    foreach my $arg (@$args) {\n        if ($arg eq \"hugepagesz=2M\") {\n            $parsed_size = 2;\n        } elsif ($arg eq \"hugepagesz=1G\") {\n            $parsed_size = 1024;\n        } elsif ($arg =~ m/^hugepages=(\\d+)?$/) {\n            if ($parsed_size == $size) {\n                return $1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nsub hugepages_pre_deallocate {\n    my ($hugepages_topology) = @_;\n\n    foreach my $size (sort keys %$hugepages_topology) {\n\n        my $hugepages_size = $size * 1024;\n        my $path = \"/sys/kernel/mm/hugepages/hugepages-${hugepages_size}kB/\";\n        my $hugepages_nr = hugepages_default_nr_hugepages($size);\n        PVE::ProcFSTools::write_proc_entry($path . \"nr_hugepages\", $hugepages_nr);\n    }\n}\n\nsub hugepages_reset {\n    my ($hugepages_topology) = @_;\n\n    foreach my $size (sort keys %$hugepages_topology) {\n\n        my $nodes = $hugepages_topology->{$size};\n        foreach my $numanode (keys %$nodes) {\n\n            my $hugepages_nr = $hugepages_topology->{$size}->{$numanode};\n            my $hugepages_size = $size * 1024;\n            my $path =\n                \"/sys/devices/system/node/node${numanode}/hugepages/hugepages-${hugepages_size}kB/\";\n\n            PVE::ProcFSTools::write_proc_entry($path . \"nr_hugepages\", $hugepages_nr);\n        }\n    }\n}\n\nsub hugepages_update_locked {\n    my ($code, @param) = @_;\n\n    my $timeout = 60; #could be long if a lot of hugepages need to be allocated\n\n    my $lock_filename = \"/var/lock/hugepages.lck\";\n\n    my $res = lock_file($lock_filename, $timeout, $code, @param);\n    die $@ if $@;\n\n    return $res;\n}\n1;\n\n"
  },
  {
    "path": "src/PVE/QemuServer/MetaInfo.pm",
    "content": "package PVE::QemuServer::MetaInfo;\n\nuse strict;\nuse warnings;\n\nuse PVE::JSONSchema;\n\nuse PVE::QemuServer::Helpers;\n\nour $meta_info_fmt = {\n    'ctime' => {\n        type => 'integer',\n        description => \"The guest creation timestamp as UNIX epoch time\",\n        minimum => 0,\n        optional => 1,\n    },\n    'creation-qemu' => {\n        type => 'string',\n        description => \"The QEMU (machine) version from the time this VM was created.\",\n        pattern => '\\d+(\\.\\d+)+',\n        optional => 1,\n    },\n};\n\nsub parse_meta_info {\n    my ($value) = @_;\n\n    return if !$value;\n\n    my $res = eval { PVE::JSONSchema::parse_property_string($meta_info_fmt, $value) };\n    warn $@ if $@;\n    return $res;\n}\n\nsub new_meta_info_string {\n    my () = @_; # for now do not allow to override any value\n\n    return PVE::JSONSchema::print_property_string(\n        {\n            'creation-qemu' => PVE::QemuServer::Helpers::kvm_user_version(),\n            ctime => \"\" . int(time()),\n        },\n        $meta_info_fmt,\n    );\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Monitor.pm",
    "content": "package PVE::QemuServer::Monitor;\n\nuse strict;\nuse warnings;\n\nuse PVE::SafeSyslog;\nuse PVE::QemuServer::Helpers;\nuse PVE::QMPClient;\n\nuse base 'Exporter';\nour @EXPORT_OK = qw(\n    mon_cmd\n    qmp_cmd\n    qsd_qmp_peer\n    vm_qmp_peer\n);\n\n=head3 qmp_cmd\n\n    my $peer = { name => $name, id => $id, type => $type };\n    my $result = qmp_cmd($peer, $execute, %arguments);\n\nExecute the C<$qmp_command_name> with arguments C<%params> for the peer C<$peer>. The type C<$type>\nof the peer can be C<qmp> for the QEMU instance of the VM,  C<qga> for the guest agent of the VM or\nC<qsd> for the QEMU storage daemon associated to the VM. Dies if the VM is not running or the\nmonitor socket cannot be reached, even if the C<noerr> argument is used. Returns the structured\nresult from the QMP side converted from JSON to structured Perl data. In case the C<noerr> argument\nis used and the QMP command failed or timed out, the result is a hash reference with an C<error> key\ncontaining the error message.\n\nParameters:\n\n=over\n\n=item C<$peer>: The peer to communicate with. A hash reference with:\n\n=over\n\n=item C<$name>: Name of the peer used in error messages.\n\n=item C<$id>: Identifier for the peer. The pair C<($id, $type)> uniquely identifies a peer.\n\n=item C<$type>: Type of the peer to communicate with. This can be C<qmp> for the VM's QEMU instance,\nC<qga> for the VM's guest agent or C<qsd> for the QEMU storage daemon associated to the VM.\n\n=back\n\n=item C<$execute>: The QMP command name.\n\n=item C<%arguments>: Additional arguments for the QMP command. The following custom arguments are\nnot part of the QMP schema and supported for all commands:\n\n=over\n\n=item C<timeout>: wait at most for this amount of time. If there was no actual error, the QMP/QGA\ncommand will still continue to be executed even after the timeout reached.\n\n=item C<noerr>: do not die when the command gets an error or the timeout is hit. The caller needs to\nhandle the error that is returned as a structured result.\n\n=back\n\n=back\n\n=cut\n\nsub qmp_cmd {\n    my ($peer, $execute, %arguments) = @_;\n\n    my $cmd = { execute => $execute, arguments => \\%arguments };\n\n    my $res;\n\n    my ($noerr, $timeout);\n    if ($cmd->{arguments}) {\n        ($noerr, $timeout) = delete($cmd->{arguments}->@{qw(noerr timeout)});\n    }\n\n    eval {\n        if ($peer->{type} eq 'qmp' || $peer->{type} eq 'qga') {\n            die \"$peer->{name} not running\\n\"\n                if !PVE::QemuServer::Helpers::vm_running_locally($peer->{id});\n        } elsif ($peer->{type} eq 'qsd') {\n            die \"$peer->{name} not running\\n\"\n                if !PVE::QemuServer::Helpers::qsd_running_locally($peer->{id});\n        } else {\n            die \"qmp_cmd - unknown peer type $peer->{type}\\n\";\n        }\n\n        my $sname = PVE::QemuServer::Helpers::qmp_socket($peer);\n        if (-e $sname) { # test if VM is reasonably new and supports qmp/qga\n            my $qmpclient = PVE::QMPClient->new();\n\n            $res = $qmpclient->cmd($peer, $cmd, $timeout, $noerr);\n        } else {\n            die \"unable to open monitor socket\\n\";\n        }\n    };\n    if (my $err = $@) {\n        syslog(\"err\", \"$peer->{name} $peer->{type} command failed - $err\");\n        die $err;\n    }\n\n    return $res;\n}\n\nsub vm_qmp_peer {\n    my ($vmid) = @_;\n\n    return { name => \"VM $vmid\", id => $vmid, type => 'qmp' };\n}\n\nsub qsd_qmp_peer {\n    my ($id) = @_;\n\n    return { name => \"QEMU storage daemon $id\", id => $id, type => 'qsd' };\n}\n\nsub qsd_cmd {\n    my ($id, $execute, %params) = @_;\n\n    return qmp_cmd(qsd_qmp_peer($id), $execute, %params);\n}\n\nsub mon_cmd {\n    my ($vmid, $execute, %params) = @_;\n\n    my $type = ($execute =~ /^guest\\-+/) ? 'qga' : 'qmp';\n\n    return qmp_cmd({ name => \"VM $vmid\", id => $vmid, type => $type }, $execute, %params);\n}\n\nsub hmp_cmd {\n    my ($vmid, $cmdline, $timeout) = @_;\n\n    return qmp_cmd(\n        vm_qmp_peer($vmid), 'human-monitor-command',\n        'command-line' => $cmdline,\n        timeout => $timeout,\n    );\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Network.pm",
    "content": "package PVE::QemuServer::Network;\n\nuse strict;\nuse warnings;\n\nuse PVE::Cluster;\nuse PVE::Firewall::Helpers;\nuse PVE::JSONSchema qw(get_standard_option parse_property_string);\nuse PVE::Network::SDN::Vnets;\nuse PVE::Network::SDN::Zones;\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Tools qw($IPV6RE file_read_firstline);\n\nuse PVE::QemuServer::Monitor qw(mon_cmd);\n\nmy $nic_model_list = [\n    'e1000',\n    'e1000-82540em',\n    'e1000-82544gc',\n    'e1000-82545em',\n    'e1000e',\n    'i82551',\n    'i82557b',\n    'i82559er',\n    'ne2k_isa',\n    'ne2k_pci',\n    'pcnet',\n    'rtl8139',\n    'virtio',\n    'vmxnet3',\n];\n\nmy $net_fmt_bridge_descr = <<__EOD__;\nBridge to attach the network device to. The Proxmox VE standard bridge\nis called 'vmbr0'.\n\nIf you do not specify a bridge, we create a kvm user (NATed) network\ndevice, which provides DHCP and DNS services. The following addresses\nare used:\n\n 10.0.2.2   Gateway\n 10.0.2.3   DNS Server\n 10.0.2.4   SMB Server\n\nThe DHCP server assign addresses to the guest starting from 10.0.2.15.\n__EOD__\n\nmy $net_fmt = {\n    macaddr => get_standard_option(\n        'mac-addr',\n        {\n            description =>\n                \"MAC address. That address must be unique within your network. This is\"\n                . \" automatically generated if not specified.\",\n        },\n    ),\n    model => {\n        type => 'string',\n        description =>\n            \"Network Card Model. The 'virtio' model provides the best performance with\"\n            . \" very low CPU overhead. If your guest does not support this driver, it is usually\"\n            . \" best to use 'e1000'.\",\n        enum => $nic_model_list,\n        default_key => 1,\n    },\n    (map { $_ => { keyAlias => 'model', alias => 'macaddr' } } @$nic_model_list),\n    bridge => get_standard_option(\n        'pve-bridge-id',\n        {\n            description => $net_fmt_bridge_descr,\n            optional => 1,\n        },\n    ),\n    queues => {\n        type => 'integer',\n        minimum => 0,\n        maximum => 64,\n        description => 'Number of packet queues to be used on the device.',\n        optional => 1,\n    },\n    rate => {\n        type => 'number',\n        minimum => 0,\n        description => \"Rate limit in mbps (megabytes per second) as floating point number.\",\n        optional => 1,\n    },\n    tag => {\n        type => 'integer',\n        minimum => 1,\n        maximum => 4094,\n        description => 'VLAN tag to apply to packets on this interface.',\n        optional => 1,\n    },\n    trunks => {\n        type => 'string',\n        pattern => qr/\\d+(?:-\\d+)?(?:;\\d+(?:-\\d+)?)*/,\n        description => 'VLAN trunks to pass through this interface.',\n        format_description => 'vlanid[;vlanid...]',\n        optional => 1,\n    },\n    firewall => {\n        type => 'boolean',\n        description => 'Whether this interface should be protected by the firewall.',\n        optional => 1,\n    },\n    link_down => {\n        type => 'boolean',\n        description => 'Whether this interface should be disconnected (like pulling the plug).',\n        optional => 1,\n    },\n    mtu => {\n        type => 'integer',\n        minimum => 1,\n        maximum => 65520,\n        description =>\n            \"Force MTU of network device (VirtIO only). Setting to '1' or empty will use the bridge MTU\",\n        optional => 1,\n    },\n};\n\nour $netdesc = {\n    optional => 1,\n    type => 'string',\n    format => $net_fmt,\n    description => \"Specify network devices.\",\n};\n\nPVE::JSONSchema::register_standard_option(\"pve-qm-net\", $netdesc);\n\nmy $ipconfig_fmt = {\n    ip => {\n        type => 'string',\n        format => 'pve-ipv4-config',\n        format_description => 'IPv4Format/CIDR',\n        description => 'IPv4 address in CIDR format.',\n        optional => 1,\n        default => 'dhcp',\n    },\n    gw => {\n        type => 'string',\n        format => 'ipv4',\n        format_description => 'GatewayIPv4',\n        description => 'Default gateway for IPv4 traffic.',\n        optional => 1,\n        requires => 'ip',\n    },\n    ip6 => {\n        type => 'string',\n        format => 'pve-ipv6-config',\n        format_description => 'IPv6Format/CIDR',\n        description => 'IPv6 address in CIDR format.',\n        optional => 1,\n        default => 'dhcp',\n    },\n    gw6 => {\n        type => 'string',\n        format => 'ipv6',\n        format_description => 'GatewayIPv6',\n        description => 'Default gateway for IPv6 traffic.',\n        optional => 1,\n        requires => 'ip6',\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-ipconfig', $ipconfig_fmt);\nour $ipconfigdesc = {\n    optional => 1,\n    type => 'string',\n    format => 'pve-qm-ipconfig',\n    description => <<'EODESCR',\ncloud-init: Specify IP addresses and gateways for the corresponding interface.\n\nIP addresses use CIDR notation, gateways are optional but need an IP of the same type specified.\n\nThe special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit\ngateway should be provided.\nFor IPv6 the special string 'auto' can be used to use stateless autoconfiguration. This requires\ncloud-init 19.4 or newer.\n\nIf cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using\ndhcp on IPv4.\nEODESCR\n};\n\n# netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate=<mbps>\nsub parse_net {\n    my ($data, $disable_mac_autogen) = @_;\n\n    my $res = eval { parse_property_string($net_fmt, $data) };\n    if ($@) {\n        warn $@;\n        return;\n    }\n    if (!defined($res->{macaddr}) && !$disable_mac_autogen) {\n        my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');\n        $res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});\n    }\n    return $res;\n}\n\n# ipconfigX ip=cidr,gw=ip,ip6=cidr,gw6=ip\nsub parse_ipconfig {\n    my ($data) = @_;\n\n    my $res = eval { parse_property_string($ipconfig_fmt, $data) };\n    if ($@) {\n        warn $@;\n        return;\n    }\n\n    if ($res->{gw} && !$res->{ip}) {\n        warn 'gateway specified without specifying an IP address';\n        return;\n    }\n    if ($res->{gw6} && !$res->{ip6}) {\n        warn 'IPv6 gateway specified without specifying an IPv6 address';\n        return;\n    }\n    if ($res->{gw} && $res->{ip} eq 'dhcp') {\n        warn 'gateway specified together with DHCP';\n        return;\n    }\n    if ($res->{gw6} && $res->{ip6} !~ /^$IPV6RE/) {\n        # gw6 + auto/dhcp\n        warn \"IPv6 gateway specified together with $res->{ip6} address\";\n        return;\n    }\n\n    if (!$res->{ip} && !$res->{ip6}) {\n        return { ip => 'dhcp', ip6 => 'dhcp' };\n    }\n\n    return $res;\n}\n\nsub print_net {\n    my $net = shift;\n\n    return PVE::JSONSchema::print_property_string($net, $net_fmt);\n}\n\nsub add_random_macs {\n    my ($settings) = @_;\n\n    foreach my $opt (keys %$settings) {\n        next if $opt !~ m/^net(\\d+)$/;\n        my $net = parse_net($settings->{$opt});\n        next if !$net;\n        $settings->{$opt} = print_net($net);\n    }\n}\n\nsub add_nets_bridge_fdb {\n    my ($conf, $vmid) = @_;\n\n    for my $opt (keys %$conf) {\n        next if $opt !~ m/^net(\\d+)$/;\n        my $iface = \"tap${vmid}i$1\";\n        # NOTE: expect setups with learning off to *not* use auto-random-generation of MAC on start\n        my $net = parse_net($conf->{$opt}, 1) or next;\n\n        my $mac = $net->{macaddr};\n        if (!$mac) {\n            log_warn(\n                \"MAC learning disabled, but vNIC '$iface' has no static MAC to add to forwarding DB!\"\n            ) if !file_read_firstline(\"/sys/class/net/$iface/brport/learning\");\n            next;\n        }\n\n        my $bridge = $net->{bridge};\n        if (!$bridge) {\n            log_warn(\"Interface '$iface' not attached to any bridge.\");\n            next;\n        }\n        PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $bridge);\n    }\n}\n\nsub del_nets_bridge_fdb {\n    my ($conf, $vmid) = @_;\n\n    for my $opt (keys %$conf) {\n        next if $opt !~ m/^net(\\d+)$/;\n        my $iface = \"tap${vmid}i$1\";\n\n        my $net = parse_net($conf->{$opt}) or next;\n        my $mac = $net->{macaddr} or next;\n\n        my $bridge = $net->{bridge};\n        PVE::Network::SDN::Zones::del_bridge_fdb($iface, $mac, $bridge);\n    }\n}\n\nsub create_ifaces_ipams_ips {\n    my ($conf, $vmid) = @_;\n\n    foreach my $opt (keys %$conf) {\n        if ($opt =~ m/^net(\\d+)$/) {\n            my $value = $conf->{$opt};\n            my $net = parse_net($value);\n            eval {\n                PVE::Network::SDN::Vnets::add_next_free_cidr(\n                    $net->{bridge}, $conf->{name}, $net->{macaddr}, $vmid, undef, 1,\n                );\n            };\n            warn $@ if $@;\n        }\n    }\n}\n\nsub delete_ifaces_ipams_ips {\n    my ($conf, $vmid) = @_;\n\n    foreach my $opt (keys %$conf) {\n        if ($opt =~ m/^net(\\d+)$/) {\n            my $net = parse_net($conf->{$opt});\n            eval {\n                PVE::Network::SDN::Vnets::del_ips_from_mac(\n                    $net->{bridge},\n                    $net->{macaddr},\n                    $conf->{name},\n                );\n            };\n            warn $@ if $@;\n        }\n    }\n}\n\nsub tap_plug {\n    my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;\n\n    $firewall = $firewall && PVE::Firewall::Helpers::needs_fwbr($bridge);\n    PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate);\n}\n\nsub get_nets_host_mtu {\n    my ($vmid, $conf) = @_;\n\n    my $nets_host_mtu = [];\n    for my $opt (sort keys $conf->%*) {\n        next if $opt !~ m/^net(\\d+)$/;\n        my $net = parse_net($conf->{$opt});\n        next if $net->{model} ne 'virtio';\n\n        my $host_mtu = eval {\n            mon_cmd(\n                $vmid, 'qom-get',\n                path => \"/machine/peripheral/$opt\",\n                property => 'host_mtu',\n            );\n        };\n        if (my $err = $@) {\n            log_warn(\"$opt: could not query host_mtu - $err\");\n        } elsif (defined($host_mtu)) {\n            push $nets_host_mtu->@*, \"${opt}=${host_mtu}\";\n        } else {\n            log_warn(\"$opt: got undefined value when querying host_mtu\");\n        }\n    }\n    return join(',', $nets_host_mtu->@*);\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/OVMF.pm",
    "content": "package PVE::QemuServer::OVMF;\n\nuse strict;\nuse warnings;\n\nuse JSON qw(to_json);\n\nuse PVE::File qw(file_exists file_get_size);\nuse PVE::GuestHelpers qw(safe_string_ne);\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::Storage;\nuse PVE::Tools;\n\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::Drive qw(checked_volume_format parse_drive print_drive);\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::QemuImage;\nuse PVE::QemuServer::QSD;\n\nmy $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';\nmy $OVMF = {\n    x86_64 => {\n        '4m-no-smm' => [\n            \"$EDK2_FW_BASE/OVMF_CODE_4M.fd\", \"$EDK2_FW_BASE/OVMF_VARS_4M.fd\",\n        ],\n        '4m-no-smm-ms' => [\n            \"$EDK2_FW_BASE/OVMF_CODE_4M.fd\", \"$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd\",\n        ],\n        '4m' => [\n            \"$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd\", \"$EDK2_FW_BASE/OVMF_VARS_4M.fd\",\n        ],\n        '4m-ms' => [\n            \"$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd\", \"$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd\",\n        ],\n        '4m-sev' => [\n            \"$EDK2_FW_BASE/OVMF_SEV_CODE_4M.fd\", \"$EDK2_FW_BASE/OVMF_SEV_VARS_4M.fd\",\n        ],\n        '4m-snp' => [\n            \"$EDK2_FW_BASE/OVMF_SEV_4M.fd\",\n        ],\n        '4m-tdx' => [\n            \"$EDK2_FW_BASE/OVMF_TDX_4M.ms.fd\",\n        ],\n        # FIXME: These are legacy 2MB-sized images that modern OVMF doesn't supports to build\n        # anymore. how can we deperacate this sanely without breaking existing instances, or using\n        # older backups and snapshot?\n        default => [\n            \"$EDK2_FW_BASE/OVMF_CODE.fd\", \"$EDK2_FW_BASE/OVMF_VARS.fd\",\n        ],\n    },\n    aarch64 => {\n        default => [\n            \"$EDK2_FW_BASE/AAVMF_CODE.fd\", \"$EDK2_FW_BASE/AAVMF_VARS.fd\",\n        ],\n    },\n};\n\nmy sub get_ovmf_files($$$$) {\n    my ($arch, $efidisk, $smm, $cvm_type) = @_;\n\n    my $types = $OVMF->{$arch}\n        or die \"no OVMF images known for architecture '$arch'\\n\";\n\n    my $type = 'default';\n    if ($arch eq 'x86_64') {\n        if ($cvm_type && $cvm_type eq 'snp') {\n            $type = \"4m-snp\";\n            my ($ovmf) = $types->{$type}->@*;\n            die \"EFI base image '$ovmf' not found\\n\" if !file_exists($ovmf);\n            return ($ovmf);\n        } elsif ($cvm_type && ($cvm_type eq 'std' || $cvm_type eq 'es')) {\n            $type = \"4m-sev\";\n        } elsif ($cvm_type && $cvm_type eq 'tdx') {\n            $type = \"4m-tdx\";\n            my ($ovmf) = $types->{$type}->@*;\n            die \"EFI base image '$ovmf' not found\\n\" if !file_exists($ovmf);\n            return ($ovmf);\n        } elsif (defined($efidisk->{efitype}) && $efidisk->{efitype} eq '4m') {\n            $type = $smm ? \"4m\" : \"4m-no-smm\";\n            $type .= '-ms' if $efidisk->{'pre-enrolled-keys'};\n        } else {\n            # TODO: log_warn about use of legacy images for x86_64 with Promxox VE 9\n        }\n    }\n\n    my ($ovmf_code, $ovmf_vars) = $types->{$type}->@*;\n    die \"EFI base image '$ovmf_code' not found\\n\" if !file_exists($ovmf_code);\n    die \"EFI vars image '$ovmf_vars' not found\\n\" if !file_exists($ovmf_vars);\n\n    return ($ovmf_code, $ovmf_vars);\n}\n\nmy sub print_ovmf_drive_commandlines {\n    my ($conf, $storecfg, $vmid, $hw_info, $version_guard, $readonly) = @_;\n\n    my ($cvm_type, $arch, $q35) = $hw_info->@{qw(cvm-type arch q35)};\n\n    my $d = $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;\n\n    die \"Attempting to configure SEV-SNP with pflash devices instead of using `-bios`\\n\"\n        if $cvm_type && $cvm_type eq 'snp';\n\n    die \"Attempting to configure TDX with pflash devices instead of using `-bios`\\n\"\n        if $cvm_type && $cvm_type eq 'tdx';\n\n    my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $d, $q35, $cvm_type);\n    my $ovmf_vars_size = file_get_size($ovmf_vars);\n\n    my $var_drive_str = \"if=pflash,unit=1,id=drive-efidisk0\";\n    if ($d) {\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);\n        my ($path, $format) = $d->@{ 'file', 'format' };\n        if ($storeid) {\n            $path = PVE::Storage::path($storecfg, $d->{file});\n            $format //= checked_volume_format($storecfg, $d->{file});\n        } elsif (!defined($format)) {\n            die \"efidisk format must be specified\\n\";\n        }\n        # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329\n        if ($path =~ m/^rbd:/) {\n            $var_drive_str .= ',cache=writeback';\n            $path .= ':rbd_cache_policy=writeback'; # avoid write-around, we *need* to cache writes too\n        }\n        $var_drive_str .= \",format=$format,file=$path\";\n\n        $var_drive_str .= \",size=\" . $ovmf_vars_size\n            if $format eq 'raw' && $version_guard->(4, 1, 2);\n        $var_drive_str .= ',readonly=on' if $readonly;\n    } else {\n        log_warn(\"no efidisk configured! Using temporary efivars disk.\");\n        my $path = \"/tmp/$vmid-ovmf.fd\";\n        PVE::Tools::file_copy($ovmf_vars, $path, $ovmf_vars_size);\n        $var_drive_str .= \",format=raw,file=$path\";\n        $var_drive_str .= \",size=\" . $ovmf_vars_size if $version_guard->(4, 1, 2);\n    }\n\n    return (\"if=pflash,unit=0,format=raw,readonly=on,file=$ovmf_code\", $var_drive_str);\n}\n\nsub get_efivars_size {\n    my ($arch, $efidisk, $smm, $cvm_type) = @_;\n\n    my (undef, $ovmf_vars) = get_ovmf_files($arch, $efidisk, $smm, $cvm_type);\n    return file_get_size($ovmf_vars);\n}\n\nmy sub is_ms_2023_cert_enrolled {\n    my ($path) = @_;\n\n    my $inside_db_section;\n    my $found_ms_2023_cert;\n\n    my $detect_ms_2023_cert = sub {\n        my ($line) = @_;\n        return if $found_ms_2023_cert;\n        $inside_db_section = undef if !$line;\n        $found_ms_2023_cert = 1\n            if $inside_db_section && $line =~ m/CN=Microsoft UEFI CA 2023/;\n        $inside_db_section = 1 if $line =~ m/^name=db guid=guid:EfiImageSecurityDatabase/;\n        return;\n    };\n\n    PVE::Tools::run_command(\n        ['virt-fw-vars', '--input', $path, '--print', '--verbose'],\n        outfunc => $detect_ms_2023_cert,\n    );\n\n    return $found_ms_2023_cert;\n}\n\nsub create_efidisk($$$$$$$$) {\n    my ($storecfg, $storeid, $vmid, $fmt, $arch, $efidisk, $smm, $cvm_type) = @_;\n\n    my (undef, $ovmf_vars) = get_ovmf_files($arch, $efidisk, $smm, $cvm_type);\n\n    my $vars_size_b = file_get_size($ovmf_vars);\n    my $vars_size = PVE::Tools::convert_size($vars_size_b, 'b' => 'kb');\n    my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);\n    PVE::Storage::activate_volumes($storecfg, [$volid]);\n\n    PVE::QemuServer::QemuImage::convert($ovmf_vars, $volid, $vars_size_b);\n    my $size = PVE::Storage::volume_size_info($storecfg, $volid, 3);\n\n    if ($efidisk->{'pre-enrolled-keys'} && is_ms_2023_cert_enrolled($ovmf_vars)) {\n        $efidisk->{'ms-cert'} = '2023k';\n    }\n\n    return ($volid, $size / 1024);\n}\n\nmy sub generate_ovmf_blockdev {\n    my ($conf, $storecfg, $vmid, $hw_info, $readonly) = @_;\n\n    my ($cvm_type, $arch, $machine_version, $q35) =\n        $hw_info->@{qw(cvm-type arch machine-version q35)};\n\n    my $drive = $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;\n\n    die \"Attempting to configure SEV-SNP with pflash devices instead of using `-bios`\\n\"\n        if $cvm_type && $cvm_type eq 'snp';\n\n    my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $drive, $q35, $cvm_type);\n\n    my $ovmf_code_blockdev = {\n        driver => 'raw',\n        file => { driver => 'file', filename => \"$ovmf_code\" },\n        'node-name' => 'pflash0',\n        'read-only' => JSON::true,\n    };\n\n    my $format;\n\n    if ($drive) {\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);\n        $format = $drive->{format};\n        if ($storeid) {\n            $format //= checked_volume_format($storecfg, $drive->{file});\n        } elsif (!defined($format)) {\n            die \"efidisk format must be specified\\n\";\n        }\n    } else {\n        log_warn(\"no efidisk configured! Using temporary efivars disk.\");\n        my $path = \"/tmp/$vmid-ovmf.fd\";\n        PVE::Tools::file_copy($ovmf_vars, $path, file_get_size($ovmf_vars));\n        $drive = { file => $path, interface => 'efidisk', index => 0 };\n        $format = 'raw';\n    }\n\n    # Prior to -blockdev, QEMU's default 'writeback' cache mode was used for EFI disks, rather than\n    # the Proxmox VE default 'none'. Use that for -blockdev too, to avoid bug #3329.\n    $drive->{cache} = 'writeback' if !$drive->{cache};\n\n    my $extra_blockdev_options = {};\n    $extra_blockdev_options->{'read-only'} = 1 if $readonly;\n\n    $extra_blockdev_options->{size} = file_get_size($ovmf_vars) if $format eq 'raw';\n\n    my $throttle_group = PVE::QemuServer::Blockdev::generate_throttle_group($drive);\n\n    my $ovmf_vars_blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(\n        $storecfg, $drive, $machine_version, $extra_blockdev_options,\n    );\n\n    return ($ovmf_code_blockdev, $ovmf_vars_blockdev, $throttle_group);\n}\n\nsub print_ovmf_commandline {\n    my ($conf, $storecfg, $vmid, $hw_info, $version_guard, $readonly) = @_;\n\n    my $cvm_type = $hw_info->{'cvm-type'};\n\n    my $cmd = [];\n    my $machine_flags = [];\n\n    if ($cvm_type && ($cvm_type eq 'snp' || $cvm_type eq 'tdx')) {\n        if (defined($conf->{efidisk0})) {\n            log_warn(\n                \"EFI disks are not supported with Confidential Virtual Machines and will be ignored\"\n            );\n        }\n        push $cmd->@*, '-bios', get_ovmf_files($hw_info->{arch}, undef, undef, $cvm_type);\n    } else {\n        if ($version_guard->(10, 0, 0)) { # for the switch to -blockdev\n            my ($code_blockdev, $vars_blockdev, $throttle_group) =\n                generate_ovmf_blockdev($conf, $storecfg, $vmid, $hw_info, $readonly);\n\n            push $cmd->@*, '-object', to_json($throttle_group, { canonical => 1 });\n            push $cmd->@*, '-blockdev', to_json($code_blockdev, { canonical => 1 });\n            push $cmd->@*, '-blockdev', to_json($vars_blockdev, { canonical => 1 });\n            push $machine_flags->@*, \"pflash0=$code_blockdev->{'node-name'}\",\n                \"pflash1=$vars_blockdev->{'node-name'}\";\n        } else {\n            my ($code_drive_str, $var_drive_str) = print_ovmf_drive_commandlines(\n                $conf, $storecfg, $vmid, $hw_info, $version_guard, $readonly,\n            );\n            push $cmd->@*, '-drive', $code_drive_str;\n            push $cmd->@*, '-drive', $var_drive_str;\n        }\n    }\n\n    return ($cmd, $machine_flags);\n}\n\nsub should_enroll_ms_2023_cert {\n    my ($efidisk) = @_;\n\n    return if !$efidisk->{'pre-enrolled-keys'};\n    return if $efidisk->{'ms-cert'} && $efidisk->{'ms-cert'} eq '2023k';\n\n    return 1;\n}\n\nsub ensure_ms_2023_cert_enrolled {\n    my ($storecfg, $vmid, $efidisk) = @_;\n\n    return if !should_enroll_ms_2023_cert($efidisk);\n\n    print \"efidisk0: enrolling Microsoft UEFI CA 2023\\n\";\n\n    my $qsd_id = \"vm-$vmid-efi-enroll\";\n    if (my $qsd_pid = PVE::QemuServer::Helpers::qsd_running_locally($qsd_id)) {\n        die \"QEMU storage daemon $qsd_id already running with PID $qsd_pid (left over process?)\\n\";\n    }\n    PVE::QemuServer::QSD::start($qsd_id);\n\n    eval {\n        # virt-fw-vars will only apply the --microsoft-kek option when combined with\n        # --enroll-{cert,generate,redhat}. That requires also specifying a platform key, so instead\n        # use the --add-kek option.\n        my $ms_2023_kek_path = '/usr/lib/python3/dist-packages/virt/firmware/certs/'\n            . 'MicrosoftCorporationKEK2KCA2023.pem';\n        # Taken from guids.py in the virt-fw-vars sources.\n        my $ms_vendor_guid = '77fa9abd-0359-4d32-bd60-28f4e78f784b';\n        my $efi_vars_path =\n            PVE::QemuServer::QSD::add_fuse_export($qsd_id, $efidisk, 'efidisk0-enroll');\n        PVE::Tools::run_command(\n            [\n                'virt-fw-vars',\n                '--inplace',\n                $efi_vars_path,\n                '--distro-keys',\n                'ms-uefi',\n                '--distro-keys',\n                'windows',\n                '--add-kek',\n                $ms_vendor_guid,\n                $ms_2023_kek_path,\n            ],\n        );\n        PVE::QemuServer::QSD::remove_fuse_export($qsd_id, 'efidisk0-enroll');\n    };\n    my $err = $@;\n\n    PVE::QemuServer::QSD::quit($qsd_id);\n\n    die \"efidisk0: enrolling Microsoft UEFI CA 2023 failed - $err\" if $err;\n\n    $efidisk->{'ms-cert'} = '2023k';\n    return $efidisk;\n}\n\nsub drive_change {\n    my ($storecfg, $vmid, $old_drive, $new_drive) = @_;\n\n    if (\n        $old_drive->{file} eq $new_drive->{file} # change affecting the same volume\n        && safe_string_ne($old_drive->{'ms-cert'}, $new_drive->{'ms-cert'}) # ms-cert changed\n        && $new_drive->{'ms-cert'}\n        && $new_drive->{'ms-cert'} =~ m/^2023/\n    ) {\n        # The ms-cert marker was newly changed to 2023, ensure it's enrolled. Clear it first to\n        # avoid detecting as already enrolled.\n        delete $new_drive->{'ms-cert'};\n        ensure_ms_2023_cert_enrolled($storecfg, $vmid, $new_drive);\n    }\n\n    # Otherwise, there is nothing special to do. Note that changing away from ms-cert=2023 is\n    # allowed too, the marker is not the source of truth.\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/PCI.pm",
    "content": "package PVE::QemuServer::PCI;\n\nuse warnings;\nuse strict;\n\nuse IO::File;\n\nuse PVE::JSONSchema;\nuse PVE::Mapping::PCI;\nuse PVE::SysFSTools;\nuse PVE::Tools;\n\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Machine;\n\nuse base 'Exporter';\n\nour @EXPORT_OK = qw(\n    print_pci_addr\n    print_pcie_addr\n    print_pcie_root_port\n    parse_hostpci\n);\n\nour $MAX_HOSTPCI_DEVICES = 16;\n\nmy $PCIRE = qr/(?:[a-f0-9]{4,}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\\.[a-f0-9])?/;\nmy $hostpci_fmt = {\n    host => {\n        default_key => 1,\n        optional => 1,\n        type => 'string',\n        pattern => qr/$PCIRE(;$PCIRE)*/,\n        format_description => 'HOSTPCIID[;HOSTPCIID2...]',\n        description => <<EODESCR,\nHost PCI device pass through. The PCI ID of a host's PCI device or a list\nof PCI virtual functions of the host. HOSTPCIID syntax is:\n\n'bus:dev.func' (hexadecimal numbers)\n\nYou can use the 'lspci' command to list existing PCI devices.\n\nEither this or the 'mapping' key must be set.\nEODESCR\n    },\n    mapping => {\n        optional => 1,\n        type => 'string',\n        format_description => 'mapping-id',\n        format => 'pve-configid',\n        description => \"The ID of a cluster wide mapping. Either this or the default-key 'host'\"\n            . \" must be set.\",\n    },\n    rombar => {\n        type => 'boolean',\n        description => \"Specify whether or not the device's ROM will be visible in the\"\n            . \" guest's memory map.\",\n        optional => 1,\n        default => 1,\n    },\n    romfile => {\n        type => 'string',\n        pattern => '[^,;]+',\n        format_description => 'string',\n        description => \"Custom pci device rom filename (must be located in /usr/share/kvm/).\",\n        optional => 1,\n    },\n    pcie => {\n        type => 'boolean',\n        description => \"Choose the PCI-express bus (needs the 'q35' machine model).\",\n        optional => 1,\n        default => 0,\n    },\n    'x-vga' => {\n        type => 'boolean',\n        description => \"Enable vfio-vga device support.\",\n        optional => 1,\n        default => 0,\n    },\n    'legacy-igd' => {\n        type => 'boolean',\n        description =>\n            \"Pass this device in legacy IGD mode, making it the primary and exclusive\"\n            . \" graphics device in the VM. Requires 'pc-i440fx' machine type and VGA set to 'none'.\",\n        optional => 1,\n        default => 0,\n    },\n    'mdev' => {\n        type => 'string',\n        format_description => 'string',\n        pattern => '[^/\\.:]+',\n        optional => 1,\n        description => <<EODESCR,\nThe type of mediated device to use.\nAn instance of this type will be created on startup of the VM and\nwill be cleaned up when the VM stops.\nEODESCR\n    },\n    'vendor-id' => {\n        type => 'string',\n        pattern => qr/^0x[0-9a-fA-F]{4}$/,\n        format_description => 'hex id',\n        optional => 1,\n        description => \"Override PCI vendor ID visible to guest\",\n    },\n    'device-id' => {\n        type => 'string',\n        pattern => qr/^0x[0-9a-fA-F]{4}$/,\n        format_description => 'hex id',\n        optional => 1,\n        description => \"Override PCI device ID visible to guest\",\n    },\n    'sub-vendor-id' => {\n        type => 'string',\n        pattern => qr/^0x[0-9a-fA-F]{4}$/,\n        format_description => 'hex id',\n        optional => 1,\n        description => \"Override PCI subsystem vendor ID visible to guest\",\n    },\n    'sub-device-id' => {\n        type => 'string',\n        pattern => qr/^0x[0-9a-fA-F]{4}$/,\n        format_description => 'hex id',\n        optional => 1,\n        description => \"Override PCI subsystem device ID visible to guest\",\n    },\n    'driver' => {\n        type => 'string',\n        optional => 1,\n        default => 'vfio',\n        enum => [qw(vfio keep)],\n        description => \"If set to 'keep' the device will neither be reset nor bound to the \"\n            . \"'vfio-pci' driver. Useful for devices that already have the correct driver loaded.\",\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);\n\nour $hostpcidesc = {\n    optional => 1,\n    type => 'string',\n    format => 'pve-qm-hostpci',\n    description => \"Map host PCI devices into guest.\",\n    verbose_description => <<EODESCR,\nMap host PCI devices into guest.\n\nNOTE: This option allows direct access to host hardware. So it is no longer\npossible to migrate such machines - use with special care.\n\nCAUTION: Experimental! User reported problems with this option.\nEODESCR\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-hostpci\", $hostpcidesc);\n\nmy $pci_addr_map;\n\nsub get_pci_addr_map {\n    $pci_addr_map = {\n        piix3 => { bus => 0, addr => 1, conflict_ok => qw(ehci) },\n        ehci => { bus => 0, addr => 1, conflict_ok => qw(piix3) }, # instead of piix3 on arm\n        vga => { bus => 0, addr => 2, conflict_ok => qw(legacy-igd) },\n        'legacy-igd' => { bus => 0, addr => 2, conflict_ok => qw(vga) }, # legacy-igd requires vga=none\n        balloon0 => { bus => 0, addr => 3 },\n        watchdog => { bus => 0, addr => 4 },\n        scsihw0 => { bus => 0, addr => 5, conflict_ok => qw(pci.3) },\n        'pci.3' => { bus => 0, addr => 5, conflict_ok => qw(scsihw0) }, # also used for virtio-scsi-single bridge\n        scsihw1 => { bus => 0, addr => 6 },\n        ahci0 => { bus => 0, addr => 7 },\n        qga0 => { bus => 0, addr => 8 },\n        spice => { bus => 0, addr => 9 },\n        virtio0 => { bus => 0, addr => 10 },\n        virtio1 => { bus => 0, addr => 11 },\n        virtio2 => { bus => 0, addr => 12 },\n        virtio3 => { bus => 0, addr => 13 },\n        virtio4 => { bus => 0, addr => 14 },\n        virtio5 => { bus => 0, addr => 15 },\n        hostpci0 => { bus => 0, addr => 16 },\n        hostpci1 => { bus => 0, addr => 17 },\n        net0 => { bus => 0, addr => 18 },\n        net1 => { bus => 0, addr => 19 },\n        net2 => { bus => 0, addr => 20 },\n        net3 => { bus => 0, addr => 21 },\n        net4 => { bus => 0, addr => 22 },\n        net5 => { bus => 0, addr => 23 },\n        vga1 => { bus => 0, addr => 24 },\n        vga2 => { bus => 0, addr => 25 },\n        vga3 => { bus => 0, addr => 26 },\n        hostpci2 => { bus => 0, addr => 27 },\n        hostpci3 => { bus => 0, addr => 28 },\n        #addr29 : usb-host (pve-usb.cfg)\n        'pci.1' => { bus => 0, addr => 30 },\n        'pci.2' => { bus => 0, addr => 31 },\n        'net6' => { bus => 1, addr => 1 },\n        'net7' => { bus => 1, addr => 2 },\n        'net8' => { bus => 1, addr => 3 },\n        'net9' => { bus => 1, addr => 4 },\n        'net10' => { bus => 1, addr => 5 },\n        'net11' => { bus => 1, addr => 6 },\n        'net12' => { bus => 1, addr => 7 },\n        'net13' => { bus => 1, addr => 8 },\n        'net14' => { bus => 1, addr => 9 },\n        'net15' => { bus => 1, addr => 10 },\n        'net16' => { bus => 1, addr => 11 },\n        'net17' => { bus => 1, addr => 12 },\n        'net18' => { bus => 1, addr => 13 },\n        'net19' => { bus => 1, addr => 14 },\n        'net20' => { bus => 1, addr => 15 },\n        'net21' => { bus => 1, addr => 16 },\n        'net22' => { bus => 1, addr => 17 },\n        'net23' => { bus => 1, addr => 18 },\n        'net24' => { bus => 1, addr => 19 },\n        'net25' => { bus => 1, addr => 20 },\n        'net26' => { bus => 1, addr => 21 },\n        'net27' => { bus => 1, addr => 22 },\n        'net28' => { bus => 1, addr => 23 },\n        'net29' => { bus => 1, addr => 24 },\n        'net30' => { bus => 1, addr => 25 },\n        'net31' => { bus => 1, addr => 26 },\n        'xhci' => { bus => 1, addr => 27 },\n        'pci.4' => { bus => 1, addr => 28 },\n        'rng0' => { bus => 1, addr => 29 },\n        'pci.2-igd' => { bus => 1, addr => 30 }, # replaces pci.2 in case a legacy IGD device is passed through\n        'virtio6' => { bus => 2, addr => 1 },\n        'virtio7' => { bus => 2, addr => 2 },\n        'virtio8' => { bus => 2, addr => 3 },\n        'virtio9' => { bus => 2, addr => 4 },\n        'virtio10' => { bus => 2, addr => 5 },\n        'virtio11' => { bus => 2, addr => 6 },\n        'virtio12' => { bus => 2, addr => 7 },\n        'virtio13' => { bus => 2, addr => 8 },\n        'virtio14' => { bus => 2, addr => 9 },\n        'virtio15' => { bus => 2, addr => 10 },\n        'ivshmem' => { bus => 2, addr => 11 },\n        'audio0' => { bus => 2, addr => 12 },\n        hostpci4 => { bus => 2, addr => 13 },\n        hostpci5 => { bus => 2, addr => 14 },\n        hostpci6 => { bus => 2, addr => 15 },\n        hostpci7 => { bus => 2, addr => 16 },\n        hostpci8 => { bus => 2, addr => 17 },\n        hostpci9 => { bus => 2, addr => 18 },\n        hostpci10 => { bus => 2, addr => 19 },\n        hostpci11 => { bus => 2, addr => 20 },\n        hostpci12 => { bus => 2, addr => 21 },\n        hostpci13 => { bus => 2, addr => 22 },\n        hostpci14 => { bus => 2, addr => 23 },\n        hostpci15 => { bus => 2, addr => 24 },\n        'virtioscsi0' => { bus => 3, addr => 1 },\n        'virtioscsi1' => { bus => 3, addr => 2 },\n        'virtioscsi2' => { bus => 3, addr => 3 },\n        'virtioscsi3' => { bus => 3, addr => 4 },\n        'virtioscsi4' => { bus => 3, addr => 5 },\n        'virtioscsi5' => { bus => 3, addr => 6 },\n        'virtioscsi6' => { bus => 3, addr => 7 },\n        'virtioscsi7' => { bus => 3, addr => 8 },\n        'virtioscsi8' => { bus => 3, addr => 9 },\n        'virtioscsi9' => { bus => 3, addr => 10 },\n        'virtioscsi10' => { bus => 3, addr => 11 },\n        'virtioscsi11' => { bus => 3, addr => 12 },\n        'virtioscsi12' => { bus => 3, addr => 13 },\n        'virtioscsi13' => { bus => 3, addr => 14 },\n        'virtioscsi14' => { bus => 3, addr => 15 },\n        'virtioscsi15' => { bus => 3, addr => 16 },\n        'virtioscsi16' => { bus => 3, addr => 17 },\n        'virtioscsi17' => { bus => 3, addr => 18 },\n        'virtioscsi18' => { bus => 3, addr => 19 },\n        'virtioscsi19' => { bus => 3, addr => 20 },\n        'virtioscsi20' => { bus => 3, addr => 21 },\n        'virtioscsi21' => { bus => 3, addr => 22 },\n        'virtioscsi22' => { bus => 3, addr => 23 },\n        'virtioscsi23' => { bus => 3, addr => 24 },\n        'virtioscsi24' => { bus => 3, addr => 25 },\n        'virtioscsi25' => { bus => 3, addr => 26 },\n        'virtioscsi26' => { bus => 3, addr => 27 },\n        'virtioscsi27' => { bus => 3, addr => 28 },\n        'virtioscsi28' => { bus => 3, addr => 29 },\n        'virtioscsi29' => { bus => 3, addr => 30 },\n        'virtioscsi30' => { bus => 3, addr => 31 },\n        'scsihw2' => { bus => 4, addr => 1 },\n        'scsihw3' => { bus => 4, addr => 2 },\n        'scsihw4' => { bus => 4, addr => 3 },\n        }\n        if !defined($pci_addr_map);\n    return $pci_addr_map;\n}\n\nsub generate_mdev_uuid {\n    my ($vmid, $index) = @_;\n    return sprintf(\"%08d-0000-0000-0000-%012d\", $index, $vmid);\n}\n\nmy $get_addr_mapping_from_id = sub {\n    my ($map, $id) = @_;\n\n    my $d = $map->{$id};\n    return if !defined($d) || !defined($d->{bus}) || !defined($d->{addr});\n\n    return { bus => $d->{bus}, addr => sprintf(\"0x%x\", $d->{addr}) };\n};\n\nsub print_pci_addr {\n    my ($id, $bridges, $arch) = @_;\n\n    die \"aarch64 cannot use IDE devices\\n\" if $arch eq 'aarch64' && $id =~ /^ide/;\n\n    my $res = '';\n\n    my $map = get_pci_addr_map();\n    if (my $d = $get_addr_mapping_from_id->($map, $id)) {\n        # Using same bus slots on all HW, so we need to check special cases here. For aarch64, the\n        # virt machine has an initial pcie.0. The other pci bridges that get added are called pci.N.\n        my $busname = $arch eq 'aarch64' && $d->{bus} eq 0 ? 'pcie' : 'pci';\n\n        $res = \",bus=$busname.$d->{bus},addr=$d->{addr}\";\n        $bridges->{ $d->{bus} } = 1 if $bridges;\n    }\n\n    return $res;\n}\n\nmy $pcie_addr_map;\n\nsub get_pcie_addr_map {\n    $pcie_addr_map = {\n        vga => { bus => 'pcie.0', addr => 1 },\n        hostpci0 => { bus => \"ich9-pcie-port-1\", addr => 0 },\n        hostpci1 => { bus => \"ich9-pcie-port-2\", addr => 0 },\n        hostpci2 => { bus => \"ich9-pcie-port-3\", addr => 0 },\n        hostpci3 => { bus => \"ich9-pcie-port-4\", addr => 0 },\n        hostpci4 => { bus => \"ich9-pcie-port-5\", addr => 0 },\n        hostpci5 => { bus => \"ich9-pcie-port-6\", addr => 0 },\n        hostpci6 => { bus => \"ich9-pcie-port-7\", addr => 0 },\n        hostpci7 => { bus => \"ich9-pcie-port-8\", addr => 0 },\n        hostpci8 => { bus => \"ich9-pcie-port-9\", addr => 0 },\n        hostpci9 => { bus => \"ich9-pcie-port-10\", addr => 0 },\n        hostpci10 => { bus => \"ich9-pcie-port-11\", addr => 0 },\n        hostpci11 => { bus => \"ich9-pcie-port-12\", addr => 0 },\n        hostpci12 => { bus => \"ich9-pcie-port-13\", addr => 0 },\n        hostpci13 => { bus => \"ich9-pcie-port-14\", addr => 0 },\n        hostpci14 => { bus => \"ich9-pcie-port-15\", addr => 0 },\n        hostpci15 => { bus => \"ich9-pcie-port-16\", addr => 0 },\n        # win7 is picky about pcie assignments\n        hostpci0bus0 => { bus => \"pcie.0\", addr => 16 },\n        hostpci1bus0 => { bus => \"pcie.0\", addr => 17 },\n        hostpci2bus0 => { bus => \"pcie.0\", addr => 18 },\n        hostpci3bus0 => { bus => \"pcie.0\", addr => 19 },\n        ivshmem => { bus => 'pcie.0', addr => 20 },\n        hostpci4bus0 => { bus => \"pcie.0\", addr => 9 },\n        hostpci5bus0 => { bus => \"pcie.0\", addr => 10 },\n        hostpci6bus0 => { bus => \"pcie.0\", addr => 11 },\n        hostpci7bus0 => { bus => \"pcie.0\", addr => 12 },\n        hostpci8bus0 => { bus => \"pcie.0\", addr => 13 },\n        hostpci9bus0 => { bus => \"pcie.0\", addr => 14 },\n        hostpci10bus0 => { bus => \"pcie.0\", addr => 15 },\n        hostpci11bus0 => { bus => \"pcie.0\", addr => 21 },\n        hostpci12bus0 => { bus => \"pcie.0\", addr => 22 },\n        hostpci13bus0 => { bus => \"pcie.0\", addr => 23 },\n        hostpci14bus0 => { bus => \"pcie.0\", addr => 24 },\n        hostpci15bus0 => { bus => \"pcie.0\", addr => 25 },\n        }\n        if !defined($pcie_addr_map);\n\n    return $pcie_addr_map;\n}\n\nsub print_pcie_addr {\n    my ($id) = @_;\n\n    my $res = '';\n\n    my $map = get_pcie_addr_map($id);\n    if (my $d = $get_addr_mapping_from_id->($map, $id)) {\n        $res = \",bus=$d->{bus},addr=$d->{addr}\";\n    }\n\n    return $res;\n}\n\n# Generates the device strings for additional pcie root ports. The first 4 pcie\n# root ports are defined in the pve-q35*.cfg files.\nsub print_pcie_root_port {\n    my ($i) = @_;\n    my $res = '';\n\n    my $root_port_addresses = {\n        4 => \"10.0\",\n        5 => \"10.1\",\n        6 => \"10.2\",\n        7 => \"10.3\",\n        8 => \"10.4\",\n        9 => \"10.5\",\n        10 => \"10.6\",\n        11 => \"10.7\",\n        12 => \"11.0\",\n        13 => \"11.1\",\n        14 => \"11.2\",\n        15 => \"11.3\",\n    };\n\n    if (defined($root_port_addresses->{$i})) {\n        my $id = $i + 1;\n        $res = \"pcie-root-port,id=ich9-pcie-port-${id}\";\n        $res .= \",addr=$root_port_addresses->{$i}\";\n        $res .= \",x-speed=16,x-width=32,multifunction=on,bus=pcie.0\";\n        $res .= \",port=${id},chassis=${id}\";\n    }\n\n    return $res;\n}\n\n# returns the parsed pci config but parses the 'host' part into\n# a list if lists into the 'id' property like this:\n#\n# {\n#   mdev => 1,\n#   rombar => ...\n#   ...\n#   ids => [\n#       # this contains a list of alternative devices,\n#       [\n#           # which are itself lists of ids for one multifunction device\n#           {\n#               id => \"0000:00:00.0\",\n#               vendor => \"...\",\n#           },\n#           {\n#               id => \"0000:00:00.1\",\n#               vendor => \"...\",\n#           },\n#       ],\n#       [\n#           ...\n#       ],\n#       ...\n#   ],\n# }\nsub parse_hostpci {\n    my ($value) = @_;\n\n    return if !$value;\n\n    my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);\n\n    my $alternatives = [];\n    my $host = delete $res->{host};\n    my $mapping = delete $res->{mapping};\n\n    die \"Cannot set both 'host' and 'mapping'.\\n\" if defined($host) && defined($mapping);\n\n    if ($mapping) {\n        # we have no ordinary pci id, must be a mapping\n        my $devices = PVE::Mapping::PCI::find_on_current_node($mapping);\n        die \"PCI device mapping not found for '$mapping'\\n\" if !$devices || !scalar($devices->@*);\n\n        my $config = PVE::Mapping::PCI::config();\n        my $mapping_cfg = $config->{ids}->{$mapping};\n        $res->{'live-migration-capable'} = 1 if $mapping_cfg->{'live-migration-capable'};\n\n        for my $device ($devices->@*) {\n            eval { PVE::Mapping::PCI::assert_valid($mapping, $device, $mapping_cfg) };\n            die \"PCI device mapping invalid (hardware probably changed): $@\\n\" if $@;\n            push $alternatives->@*, [split(/;/, $device->{path})];\n        }\n    } elsif ($host) {\n        push $alternatives->@*, [split(/;/, $host)];\n    } else {\n        die \"Either 'host' or 'mapping' must be set.\\n\";\n    }\n\n    $res->{ids} = [];\n    for my $alternative ($alternatives->@*) {\n        my $ids = [];\n        foreach my $id ($alternative->@*) {\n            my $devs = PVE::SysFSTools::lspci($id);\n            die \"no PCI device found for '$id'\\n\" if !scalar($devs->@*);\n            push $ids->@*, @$devs;\n        }\n        if (scalar($ids->@*) > 1) {\n            $res->{'has-multifunction'} = 1;\n            die \"cannot use mediated device with multifunction device\\n\"\n                if $res->{mdev} || $res->{nvidia};\n        } elsif ($res->{mdev}) {\n            if ($ids->[0]->{nvidia} && $res->{mdev} =~ m/^nvidia-(\\d+)$/) {\n                $res->{nvidia} = $1;\n                delete $res->{mdev};\n            }\n        }\n        push $res->{ids}->@*, $ids;\n    }\n\n    return $res;\n}\n\n# parses all hostpci devices from a config and does some sanity checks\n# returns a hash like this:\n# {\n#     hostpci0 => {\n#         # hash from parse_hostpci function\n#     },\n#     hostpci1 => { ... },\n#     ...\n# }\nsub parse_hostpci_devices {\n    my ($conf) = @_;\n\n    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n    my $legacy_igd = 0;\n\n    my $parsed_devices = {};\n    for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {\n        my $id = \"hostpci$i\";\n        my $d = parse_hostpci($conf->{$id});\n        next if !$d;\n\n        # check syntax\n        die \"q35 machine model is not enabled\" if !$q35 && $d->{pcie};\n\n        if ($d->{'legacy-igd'}) {\n            die \"only one device can be assigned in legacy-igd mode\\n\"\n                if $legacy_igd;\n            $legacy_igd = 1;\n\n            die \"legacy IGD assignment requires VGA mode to be 'none'\\n\"\n                if !defined($conf->{'vga'}) || $conf->{'vga'} ne 'none';\n            die \"legacy IGD assignment requires rombar to be enabled\\n\"\n                if defined($d->{rombar}) && !$d->{rombar};\n            die \"legacy IGD assignment is not compatible with x-vga\\n\"\n                if $d->{'x-vga'};\n            die \"legacy IGD assignment is not compatible with mdev\\n\"\n                if $d->{mdev} || $d->{nvidia};\n            die \"legacy IGD assignment is not compatible with q35\\n\"\n                if $q35;\n            die \"legacy IGD assignment is not compatible with multifunction devices\\n\"\n                if $d->{'has-multifunction'};\n            die \"legacy IGD assignment is not compatible with alternate devices\\n\"\n                if scalar($d->{ids}->@*) > 1;\n            # check first device for valid id\n            die \"legacy IGD assignment only works for devices on host bus 00:02.0\\n\"\n                if $d->{ids}->[0]->[0]->{id} !~ m/02\\.0$/;\n        }\n\n        $parsed_devices->{$id} = $d;\n    }\n\n    return $parsed_devices;\n}\n\n# set vgpu type of a vf of an nvidia gpu with kernel 6.8 or newer\nmy sub create_nvidia_device {\n    my ($id, $model) = @_;\n\n    $id = PVE::SysFSTools::normalize_pci_id($id);\n\n    my $creation = \"/sys/bus/pci/devices/$id/nvidia/current_vgpu_type\";\n\n    die \"no nvidia sysfs api for '$id'\\n\" if !-f $creation;\n\n    my $current = PVE::Tools::file_read_firstline($creation);\n    if ($current ne \"0\") {\n        return 1 if $current eq $model;\n        # reset vgpu type so we can see all available and set the real device\n        die \"unable to reset vgpu type for '$id'\\n\" if !PVE::SysFSTools::file_write($creation, \"0\");\n    }\n\n    my $types = PVE::SysFSTools::get_mdev_types($id);\n    my $selected;\n    for my $type_definition ($types->@*) {\n        next if $type_definition->{type} ne \"nvidia-$model\";\n        $selected = $type_definition;\n    }\n\n    if (!defined($selected) || $selected->{available} < 1) {\n        die \"vgpu type '$model' not available for '$id'\\n\";\n    }\n\n    if (!PVE::SysFSTools::file_write($creation, $model)) {\n        die \"could not set vgpu type to '$model' for '$id'\\n\";\n    }\n\n    return 1;\n}\n\n# takes the hash returned by parse_hostpci_devices and for all non mdev gpus,\n# selects one of the given alternatives by trying to reserve it\n#\n# mdev devices must be chosen later when we actually allocate it, but we\n# flatten the inner list since there can only be one device per alternative anyway\nsub choose_hostpci_devices {\n    my ($devices, $vmid, $dry_run) = @_;\n\n    my $used = {};\n\n    my $add_used_device = sub {\n        my ($devices) = @_;\n        for my $used_device ($devices->@*) {\n            my $used_id = $used_device->{id};\n            die \"device '$used_id' assigned more than once\\n\" if $used->{$used_id};\n            $used->{$used_id} = 1;\n        }\n    };\n\n    for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {\n        my $device = $devices->{\"hostpci$i\"};\n        next if !$device;\n\n        if ($device->{mdev} && !$device->{nvidia}) {\n            $device->{ids} = [map { $_->[0] } $device->{ids}->@*];\n            next;\n        }\n\n        if (scalar($device->{ids}->@* == 1)) {\n            # we only have one alternative, use that\n            $device->{ids} = $device->{ids}->[0];\n            $add_used_device->($device->{ids});\n            if ($device->{nvidia} && !$dry_run) {\n                reserve_pci_usage($device->{ids}->[0]->{id}, $vmid, 10, undef);\n                create_nvidia_device($device->{ids}->[0]->{id}, $device->{nvidia});\n            }\n            next;\n        }\n\n        my $found = 0;\n        for my $alternative ($device->{ids}->@*) {\n            my $ids = [map { $_->{id} } @$alternative];\n\n            next if grep { defined($used->{$_}) } @$ids; # already used\n            if (!$dry_run) {\n                eval { reserve_pci_usage($ids, $vmid, 10, undef) };\n                next if $@;\n            }\n\n            if ($device->{nvidia} && !$dry_run) {\n                eval { create_nvidia_device($ids->[0], $device->{nvidia}) };\n                if (my $err = $@) {\n                    warn $err;\n                    remove_pci_reservation($vmid, $ids);\n                    next;\n                }\n            }\n\n            # found one that is not used or reserved\n            $add_used_device->($alternative);\n            $device->{ids} = $alternative;\n            $found = 1;\n            last;\n        }\n        die \"could not find a free device for 'hostpci$i'\\n\" if !$found;\n    }\n\n    return $devices;\n}\n\nsub print_hostpci_devices {\n    my ($vmid, $conf, $devices, $vga, $winversion, $bridges, $arch, $bootorder, $dry_run) = @_;\n\n    my $kvm_off = 0;\n    my $gpu_passthrough = 0;\n    my $legacy_igd = 0;\n\n    my $pciaddr;\n    my $pci_devices = choose_hostpci_devices(parse_hostpci_devices($conf), $vmid, $dry_run);\n\n    for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {\n        my $id = \"hostpci$i\";\n        my $d = $pci_devices->{$id};\n        next if !$d;\n\n        $legacy_igd = 1 if $d->{'legacy-igd'};\n\n        if (my $pcie = $d->{pcie}) {\n            # win7 wants to have the pcie devices directly on the pcie bus\n            # instead of in the root port\n            if ($winversion == 7) {\n                $pciaddr = print_pcie_addr(\"${id}bus0\");\n            } else {\n                # add more root ports if needed, 4 are present by default\n                # by pve-q35 cfgs, rest added here on demand.\n                if ($i > 3) {\n                    push @$devices, '-device', print_pcie_root_port($i);\n                }\n                $pciaddr = print_pcie_addr($id);\n            }\n        } else {\n            my $pci_name = $d->{'legacy-igd'} ? 'legacy-igd' : $id;\n            $pciaddr = print_pci_addr($pci_name, $bridges, $arch);\n        }\n\n        my $num_devices = scalar($d->{ids}->@*);\n        my $multifunction = $num_devices > 1 && !$d->{mdev};\n\n        my $xvga = '';\n        if ($d->{'x-vga'}) {\n            $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');\n            $kvm_off = 1;\n            $vga->{type} = 'none' if !defined($conf->{vga});\n            $gpu_passthrough = 1;\n        }\n\n        my $sysfspath;\n        if ($d->{mdev}) {\n            my $uuid = generate_mdev_uuid($vmid, $i);\n            $sysfspath = \"/sys/bus/mdev/devices/$uuid\";\n        }\n\n        for (my $j = 0; $j < $num_devices; $j++) {\n            my $pcidevice = $d->{ids}->[$j];\n            my $devicestr = \"vfio-pci\";\n\n            if ($sysfspath) {\n                $devicestr .= \",sysfsdev=$sysfspath\";\n            } else {\n                $devicestr .= \",host=$pcidevice->{id}\";\n            }\n\n            if ($d->{'live-migration-capable'}) {\n                $devicestr .= \",enable-migration=on\";\n            }\n\n            my $mf_addr = $multifunction ? \".$j\" : '';\n            $devicestr .= \",id=${id}${mf_addr}${pciaddr}${mf_addr}\";\n\n            if ($j == 0) {\n                $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};\n                $devicestr .= \"$xvga\";\n                $devicestr .= \",multifunction=on\" if $multifunction;\n                $devicestr .= \",romfile=/usr/share/kvm/$d->{romfile}\" if $d->{romfile};\n                $devicestr .= \",bootindex=$bootorder->{$id}\" if $bootorder->{$id};\n                for my $option (qw(vendor-id device-id sub-vendor-id sub-device-id)) {\n                    $devicestr .= \",x-pci-$option=$d->{$option}\" if $d->{$option};\n                }\n            }\n\n            push @$devices, '-device', $devicestr;\n            last if $d->{mdev};\n        }\n    }\n\n    return ($kvm_off, $gpu_passthrough, $legacy_igd, $pci_devices);\n}\n\nsub prepare_pci_device {\n    my ($vmid, $pciid, $index, $device) = @_;\n\n    my $info = PVE::SysFSTools::pci_device_info(\"$pciid\");\n    die \"cannot prepare PCI pass-through, IOMMU not present\\n\"\n        if !PVE::SysFSTools::check_iommu_support();\n    die \"no pci device info for device '$pciid'\\n\" if !$info;\n\n    my $driver = $device->{driver} // 'vfio';\n    if ($device->{nvidia} || $driver eq \"keep\") {\n        # nothing to do\n    } elsif (my $mdev = $device->{mdev}) {\n        my $uuid = generate_mdev_uuid($vmid, $index);\n        PVE::SysFSTools::pci_create_mdev_device($pciid, $uuid, $mdev);\n    } else {\n        die \"can't unbind/bind PCI group to VFIO '$pciid'\\n\"\n            if !PVE::SysFSTools::pci_dev_group_bind_to_vfio($pciid);\n        warn\n            \"failed to reset PCI device '$pciid', but trying to continue as not all devices need a reset\\n\"\n            if $info->{has_fl_reset} && !PVE::SysFSTools::pci_dev_reset($info);\n    }\n\n    return $info;\n}\n\nmy $RUNDIR = '/run/qemu-server';\nmy $PCIID_RESERVATION_FILE = \"${RUNDIR}/pci-id-reservations\";\nmy $PCIID_RESERVATION_LOCK = \"${PCIID_RESERVATION_FILE}.lock\";\n\n# a list of PCI ID to VMID reservations, the validity is protected against leakage by either a PID,\n# for successfully started VM processes, or a expiration time for the initial time window between\n# reservation and actual VM process start-up.\nmy $parse_pci_reservation_unlocked = sub {\n    my $pci_ids = {};\n    if (my $fh = IO::File->new($PCIID_RESERVATION_FILE, \"r\")) {\n        while (my $line = <$fh>) {\n            if ($line =~ m/^($PCIRE)\\s(\\d+)\\s(time|pid)\\:(\\d+)$/) {\n                $pci_ids->{$1} = {\n                    vmid => $2,\n                    \"$3\" => $4,\n                };\n            }\n        }\n    }\n    return $pci_ids;\n};\n\nmy $write_pci_reservation_unlocked = sub {\n    my ($reservations) = @_;\n\n    my $data = \"\";\n    for my $pci_id (sort keys $reservations->%*) {\n        my ($vmid, $pid, $time) = $reservations->{$pci_id}->@{ 'vmid', 'pid', 'time' };\n        if (defined($pid)) {\n            $data .= \"$pci_id $vmid pid:$pid\\n\";\n        } else {\n            $data .= \"$pci_id $vmid time:$time\\n\";\n        }\n    }\n    PVE::Tools::file_set_contents($PCIID_RESERVATION_FILE, $data);\n};\n\n# removes all PCI device reservations held by the `vmid`\nsub remove_pci_reservation {\n    my ($vmid, $pci_ids) = @_;\n\n    PVE::Tools::lock_file(\n        $PCIID_RESERVATION_LOCK,\n        2,\n        sub {\n            my $reservation_list = $parse_pci_reservation_unlocked->();\n            for my $id (keys %$reservation_list) {\n                next if defined($pci_ids) && !grep { $_ eq $id } $pci_ids->@*;\n                my $reservation = $reservation_list->{$id};\n                next if $reservation->{vmid} != $vmid;\n                delete $reservation_list->{$id};\n            }\n            $write_pci_reservation_unlocked->($reservation_list);\n        },\n    );\n    die $@ if $@;\n}\n\n# return all currently reserved ids from the given vmid\nsub get_reservations {\n    my ($vmid) = @_;\n\n    my $reservations = $parse_pci_reservation_unlocked->();\n\n    my $list = [];\n\n    for my $pci_id (sort keys $reservations->%*) {\n        push $list->@*, $pci_id if $reservations->{$pci_id}->{vmid} == $vmid;\n    }\n\n    return $list;\n}\n\nsub reserve_pci_usage {\n    my ($requested_ids, $vmid, $timeout, $pid) = @_;\n\n    $requested_ids = [$requested_ids] if !ref($requested_ids);\n    return if !scalar(@$requested_ids); # do nothing for empty list\n\n    PVE::Tools::lock_file(\n        $PCIID_RESERVATION_LOCK,\n        5,\n        sub {\n            my $reservation_list = $parse_pci_reservation_unlocked->();\n\n            my $ctime = time();\n            for my $id ($requested_ids->@*) {\n                my $reservation = $reservation_list->{$id};\n                if ($reservation && $reservation->{vmid} != $vmid) {\n                    # check time based reservation\n                    die\n                        \"PCI device '$id' is currently reserved for use by VMID '$reservation->{vmid}'\\n\"\n                        if defined($reservation->{time}) && $reservation->{time} > $ctime;\n\n                    if (my $reserved_pid = $reservation->{pid}) {\n                        # check running vm\n                        my $running_pid =\n                            PVE::QemuServer::Helpers::vm_running_locally($reservation->{vmid});\n                        if (defined($running_pid) && $running_pid == $reserved_pid) {\n                            die\n                                \"PCI device '$id' already in use by VMID '$reservation->{vmid}'\\n\";\n                        } else {\n                            warn \"leftover PCI reservation found for $id, lets take it...\\n\";\n                        }\n                    }\n                } elsif ($reservation) {\n                    # already reserved by the same vmid\n                    if (my $reserved_time = $reservation->{time}) {\n                        if (defined($timeout)) {\n                            # use the longer timeout\n                            my $old_timeout = $reservation->{time} - 5 - $ctime;\n                            $timeout = $old_timeout if $old_timeout > $timeout;\n                        }\n                    } elsif (my $reserved_pid = $reservation->{pid}) {\n                        my $running_pid =\n                            PVE::QemuServer::Helpers::vm_running_locally($reservation->{vmid});\n                        if (defined($running_pid) && $running_pid == $reservation->{pid}) {\n                            if (defined($pid)) {\n                                die\n                                    \"PCI device '$id' already in use by running VMID '$reservation->{vmid}'\\n\";\n                            } elsif (defined($timeout)) {\n                                # ignore timeout reservation for running vms, can happen with e.g.\n                                # qm showcmd\n                                return;\n                            }\n                        }\n                    }\n                }\n\n                $reservation_list->{$id} = { vmid => $vmid };\n                if (defined($pid)) { # VM started up, we can reserve now with the actual PID\n                    $reservation_list->{$id}->{pid} = $pid;\n                } elsif (defined($timeout)) { # temporary reserve as we don't now the PID yet\n                    $reservation_list->{$id}->{time} = $ctime + $timeout + 5;\n                }\n            }\n            $write_pci_reservation_unlocked->($reservation_list);\n        },\n    );\n    die $@ if $@;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/QMPHelpers.pm",
    "content": "package PVE::QemuServer::QMPHelpers;\n\nuse warnings;\nuse strict;\n\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Monitor qw(mon_cmd);\n\nuse base 'Exporter';\n\nour @EXPORT_OK = qw(\n    qemu_deviceadd\n    qemu_devicedel\n    qemu_objectadd\n    qemu_objectdel\n);\n\nsub nbd_stop {\n    my ($vmid) = @_;\n\n    mon_cmd($vmid, 'nbd-server-stop', timeout => 25);\n}\n\nsub qemu_deviceadd {\n    my ($vmid, $devicefull) = @_;\n\n    $devicefull = \"driver=\" . $devicefull;\n\n    PVE::QemuServer::Monitor::hmp_cmd($vmid, \"device_add $devicefull\", 25);\n}\n\nsub qemu_devicedel {\n    my ($vmid, $deviceid) = @_;\n\n    PVE::QemuServer::Monitor::hmp_cmd($vmid, \"device_del $deviceid\", 25);\n}\n\nsub qemu_objectadd {\n    my ($vmid, $objectid, $qomtype, %args) = @_;\n\n    mon_cmd($vmid, \"object-add\", id => $objectid, \"qom-type\" => $qomtype, %args);\n\n    return 1;\n}\n\nsub qemu_objectdel {\n    my ($vmid, $objectid) = @_;\n\n    mon_cmd($vmid, \"object-del\", id => $objectid);\n\n    return 1;\n}\n\n# dies if a) VM not running or not existing b) Version query failed\n# So, any defined return value is valid, any invalid state can be caught by eval\nsub runs_at_least_qemu_version {\n    my ($vmid, $major, $minor, $extra) = @_;\n\n    my $v = PVE::QemuServer::Monitor::mon_cmd($vmid, 'query-version');\n    die \"could not query currently running version for VM $vmid\\n\" if !defined($v);\n    $v = $v->{qemu};\n\n    return PVE::QemuServer::Helpers::version_cmp(\n        $v->{major}, $major, $v->{minor}, $minor, $v->{micro}, $extra,\n    ) >= 0;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/QSD.pm",
    "content": "package PVE::QemuServer::QSD;\n\nuse v5.36;\n\nuse JSON qw(to_json);\n\nuse PVE::JSONSchema qw(json_bool);\nuse PVE::SafeSyslog qw(syslog);\nuse PVE::Storage;\nuse PVE::Tools;\n\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Monitor qw(qsd_qmp_peer);\n\n=head3 start\n\n    PVE::QemuServer::QSD::start($id);\n\nStart a QEMU storage daemon instance with ID C<$id>.\n\n=cut\n\nsub start($id) {\n    # If something is still mounted, that could block the new instance, try to clean up first.\n    PVE::QemuServer::Helpers::qsd_fuse_export_cleanup_files($id);\n\n    my $qmp_socket_path = PVE::QemuServer::Helpers::qmp_socket(qsd_qmp_peer($id));\n    my $pidfile = PVE::QemuServer::Helpers::qsd_pidfile_name($id);\n\n    my $cmd = [\n        'qemu-storage-daemon',\n        '--daemonize',\n        '--chardev',\n        \"socket,id=qmp,path=$qmp_socket_path,server=on,wait=off\",\n        '--monitor',\n        'chardev=qmp,mode=control',\n        '--pidfile',\n        $pidfile,\n    ];\n\n    PVE::Tools::run_command($cmd);\n\n    my $pid = PVE::QemuServer::Helpers::qsd_running_locally($id);\n    syslog(\"info\", \"QEMU storage daemon $id started with PID $pid.\");\n\n    return;\n}\n\n=head3 add_fuse_export\n\n    my $path = PVE::QemuServer::QSD::add_fuse_export($id, $drive, $name);\n\nAttach drive C<$drive> to the storage daemon with ID C<$id> and export it with name C<$name> via\nFUSE. Returns the path to the file representing the export.\n\n=cut\n\nsub add_fuse_export($id, $drive, $name) {\n    my $storage_config = PVE::Storage::config();\n\n    PVE::Storage::activate_volumes($storage_config, [$drive->{file}]);\n\n    my ($node_name, $read_only) =\n        PVE::QemuServer::Blockdev::attach($storage_config, $id, $drive, { qsd => 1 });\n\n    my $fuse_path = PVE::QemuServer::Helpers::qsd_fuse_export_path($id, $name);\n    PVE::Tools::file_set_contents($fuse_path, '', 0600); # mountpoint file needs to exist up-front\n\n    my $export = {\n        type => 'fuse',\n        id => \"$name\",\n        mountpoint => $fuse_path,\n        'node-name' => \"$node_name\",\n        writable => json_bool(!$read_only),\n        growable => JSON::false,\n        'allow-other' => 'off',\n    };\n\n    PVE::QemuServer::Monitor::qsd_cmd($id, 'block-export-add', $export->%*);\n\n    return $fuse_path;\n}\n\n=head3 remove_fuse_export\n\n    PVE::QemuServer::QSD::remove_fuse_export($id, $name);\n\nRemove the export with name C<$name> from the storage daemon with ID C<$id>.\n\n=cut\n\nsub remove_fuse_export($id, $name) {\n    PVE::QemuServer::Monitor::qsd_cmd($id, 'block-export-del', id => \"$name\");\n\n    return;\n}\n\n=head3 quit\n\n    PVE::QemuServer::QSD::quit($id);\n\nShut down the QEMU storage daemon with ID C<$id> and cleans up its PID file and socket. Waits for 60\nseconds for clean shutdown, then sends SIGTERM and waits an additional 10 seconds before sending\nSIGKILL.\n\n=cut\n\nsub quit($id) {\n    my $name = \"QEMU storage daemon $id\";\n\n    eval { PVE::QemuServer::Monitor::qsd_cmd($id, 'quit'); };\n    my $qmp_err = $@;\n    warn \"$name failed to handle 'quit' - $qmp_err\" if $qmp_err;\n\n    my $count = $qmp_err ? 60 : 0; # can't wait for QMP 'quit' to terminate the process if it failed\n    my $pid = PVE::QemuServer::Helpers::qsd_running_locally($id);\n    while ($pid) {\n        if ($count == 60) {\n            warn \"$name still running with PID $pid - terminating now with SIGTERM\\n\";\n            kill 15, $pid;\n        } elsif ($count == 70) {\n            warn \"$name still running with PID $pid - terminating now with SIGKILL\\n\";\n            kill 9, $pid;\n            last;\n        }\n\n        sleep 1;\n        $count++;\n        $pid = PVE::QemuServer::Helpers::qsd_running_locally($id);\n    }\n\n    unlink PVE::QemuServer::Helpers::qsd_pidfile_name($id);\n    unlink PVE::QemuServer::Helpers::qmp_socket(qsd_qmp_peer($id));\n\n    PVE::QemuServer::Helpers::qsd_fuse_export_cleanup_files($id);\n\n    return;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/QemuImage.pm",
    "content": "package PVE::QemuServer::QemuImage;\n\nuse strict;\nuse warnings;\n\nuse Fcntl qw(S_ISBLK);\nuse File::stat;\nuse JSON;\n\nuse PVE::Format qw(render_bytes);\nuse PVE::Storage;\nuse PVE::Tools;\n\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::Drive qw(checked_volume_format);\nuse PVE::QemuServer::Helpers;\n\nsub convert_iscsi_path {\n    my ($path) = @_;\n\n    if ($path =~ m|^iscsi://([^/]+)/([^/]+)/(.+)$|) {\n        my $portal = $1;\n        my $target = $2;\n        my $lun = $3;\n\n        my $initiator_name = PVE::QemuServer::Helpers::get_iscsi_initiator_name();\n\n        return \"file.driver=iscsi,file.transport=tcp,file.initiator-name=$initiator_name,\"\n            . \"file.portal=$portal,file.target=$target,file.lun=$lun,driver=raw\";\n    }\n\n    die \"cannot convert iscsi path '$path', unknown format\\n\";\n}\n\nmy sub qcow2_target_image_opts {\n    my ($storecfg, $drive, $qcow2_opts, $zeroinit) = @_;\n\n    # There is no machine version, the qemu-img binary version is what's important.\n    my $version = PVE::QemuServer::Helpers::kvm_user_version();\n\n    my $blockdev_opts = { 'no-throttle' => 1 };\n    $blockdev_opts->{'zero-initialized'} = 1 if $zeroinit;\n\n    my $blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(\n        $storecfg, $drive, $version, $blockdev_opts,\n    );\n\n    my $opts = [];\n    my $opt_prefix = '';\n    my $next_child = $blockdev;\n    while ($next_child) {\n        my $current = $next_child;\n        $next_child = delete($current->{file});\n\n        # TODO should cache settings be configured here (via appropriate drive configuration) rather\n        # than via dedicated qemu-img options?\n        delete($current->{cache});\n        # TODO e.g. can't use aio 'native' without cache.direct, just use QEMU default like for\n        # other targets for now\n        delete($current->{aio});\n\n        # no need for node names\n        delete($current->{'node-name'});\n\n        # it's the write target, while the flag should be 'false' anyways, remove to be sure\n        delete($current->{'read-only'});\n\n        # TODO should those be set (via appropriate drive configuration)?\n        delete($current->{'detect-zeroes'});\n        delete($current->{'discard'});\n\n        for my $key (sort keys $current->%*) {\n            my $value;\n            if (ref($current->{$key})) {\n                if ($current->{$key} eq JSON::false) {\n                    $value = 'false';\n                } elsif ($current->{$key} eq JSON::true) {\n                    $value = 'true';\n                } else {\n                    die \"target image options: unhandled structured key: $key\\n\";\n                }\n            } else {\n                $value = $current->{$key};\n            }\n            push $opts->@*, \"$opt_prefix$key=$value\";\n        }\n\n        $opt_prefix .= 'file.';\n    }\n\n    return join(',', $opts->@*);\n}\n\n# The possible options are:\n# bwlimit - The bandwidth limit in KiB/s.\n# is-zero-initialized - If the destination image is zero-initialized.\n# snapname - Use this snapshot of the source image.\n# source-path-format - Indicate the format of the source when the source is a path. For PVE-managed\n# volumes, the format from the storage layer is always used.\nsub convert {\n    my ($src_volid, $dst_volid, $size, $opts) = @_;\n\n    my ($bwlimit, $snapname) = $opts->@{qw(bwlimit snapname)};\n\n    my $storecfg = PVE::Storage::config();\n    my ($src_storeid) = PVE::Storage::parse_volume_id($src_volid, 1);\n    my ($dst_storeid) = PVE::Storage::parse_volume_id($dst_volid, 1);\n\n    die \"destination '$dst_volid' is not a valid volid form qemu-img convert\\n\" if !$dst_storeid;\n\n    my $cachemode;\n    my $src_path;\n    my $src_is_iscsi = 0;\n    my $src_format;\n\n    if ($src_storeid) {\n        PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);\n        my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);\n        $src_format = checked_volume_format($storecfg, $src_volid);\n        $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);\n        $src_is_iscsi = ($src_path =~ m|^iscsi://|);\n        $cachemode = 'none' if $src_scfg->{type} eq 'zfspool';\n    } elsif (-f $src_volid || -b $src_volid) {\n        $src_path = $src_volid;\n        if ($opts->{'source-path-format'}) {\n            $src_format = $opts->{'source-path-format'};\n        } elsif ($src_path =~ m/\\.($PVE::QemuServer::Drive::QEMU_FORMAT_RE)$/) {\n            $src_format = $1;\n        }\n    }\n\n    die \"source '$src_volid' is not a valid volid nor path for qemu-img convert\\n\" if !$src_path;\n\n    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);\n    my $dst_format = checked_volume_format($storecfg, $dst_volid);\n    my $dst_path = PVE::Storage::path($storecfg, $dst_volid);\n    my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);\n    my $dst_needs_discard_no_unref =\n        $dst_scfg->{'snapshot-as-volume-chain'} && $dst_format eq 'qcow2';\n    my $support_qemu_snapshots = PVE::Storage::volume_qemu_snapshot_method($storecfg, $src_volid);\n\n    my $cmd = [];\n    push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';\n    push @$cmd, '-l', \"snapshot.name=$snapname\"\n        if $snapname\n        && $src_format eq 'qcow2'\n        && $support_qemu_snapshots\n        && $support_qemu_snapshots eq 'qemu';\n    push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';\n    push @$cmd, '-T', $cachemode if defined($cachemode);\n    push @$cmd, '-r', \"${bwlimit}K\" if defined($bwlimit);\n\n    if ($src_is_iscsi) {\n        push @$cmd, '--image-opts';\n        $src_path = convert_iscsi_path($src_path);\n    } elsif ($src_format) {\n        push @$cmd, '-f', $src_format;\n    }\n\n    my $dst_uses_target_image_opts = $dst_is_iscsi || $dst_needs_discard_no_unref;\n    push @$cmd, '--target-image-opts' if $dst_uses_target_image_opts;\n\n    if ($dst_is_iscsi) {\n        $dst_path = convert_iscsi_path($dst_path);\n    } elsif ($dst_needs_discard_no_unref) {\n        # don't use any other drive options, those are intended for use with a running VM and just\n        # use scsi0 as a dummy interface+index for now\n        my $dst_drive = { file => $dst_volid, interface => 'scsi', index => 0 };\n        $dst_path = qcow2_target_image_opts(\n            $storecfg,\n            $dst_drive,\n            ['discard-no-unref=true'],\n            $opts->{'is-zero-initialized'},\n        );\n    } else {\n        push @$cmd, '-O', $dst_format;\n    }\n\n    push @$cmd, $src_path;\n\n    if (!$dst_uses_target_image_opts && $opts->{'is-zero-initialized'}) {\n        push @$cmd, \"zeroinit:$dst_path\";\n    } else {\n        push @$cmd, $dst_path;\n    }\n\n    my $parser = sub {\n        my $line = shift;\n        if ($line =~ m/\\((\\S+)\\/100\\%\\)/) {\n            my $percent = $1;\n            my $transferred = int($size * $percent / 100);\n            my $total_h = render_bytes($size, 1);\n            my $transferred_h = render_bytes($transferred, 1);\n\n            print \"transferred $transferred_h of $total_h ($percent%)\\n\";\n        }\n\n    };\n\n    eval { PVE::Tools::run_command($cmd, timeout => undef, outfunc => $parser); };\n    my $err = $@;\n    die \"copy failed: $err\" if $err;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/RNG.pm",
    "content": "package PVE::QemuServer::RNG;\n\nuse strict;\nuse warnings;\n\nuse PVE::JSONSchema;\nuse PVE::Tools qw(file_read_firstline);\n\nuse PVE::QemuServer::PCI qw(print_pci_addr);\n\nuse base 'Exporter';\n\nour @EXPORT_OK = qw(\n    parse_rng\n    check_rng_source\n    print_rng_device_commandline\n    print_rng_object_commandline\n);\n\nmy $rng_fmt = {\n    source => {\n        type => 'string',\n        enum => ['/dev/urandom', '/dev/random', '/dev/hwrng'],\n        default_key => 1,\n        description => \"The file on the host to gather entropy from. Using urandom does *not*\"\n            . \" decrease security in any meaningful way, as it's still seeded from real entropy, and\"\n            . \" the bytes provided will most likely be mixed with real entropy on the guest as well.\"\n            . \" '/dev/hwrng' can be used to pass through a hardware RNG from the host.\",\n    },\n    max_bytes => {\n        type => 'integer',\n        description => \"Maximum bytes of entropy allowed to get injected into the guest every\"\n            . \" 'period' milliseconds. Use `0` to disable limiting (potentially dangerous!).\",\n        optional => 1,\n\n        # default is 1 KiB/s, provides enough entropy to the guest to avoid boot-starvation issues\n        # (e.g. systemd etc...) while allowing no chance of overwhelming the host, provided we're\n        # reading from /dev/urandom\n        default => 1024,\n    },\n    period => {\n        type => 'integer',\n        description =>\n            \"Every 'period' milliseconds the entropy-injection quota is reset, allowing\"\n            . \" the guest to retrieve another 'max_bytes' of entropy.\",\n        optional => 1,\n        default => 1000,\n    },\n};\n\nPVE::JSONSchema::register_format('pve-qm-rng', $rng_fmt);\n\nour $rngdesc = {\n    type => 'string',\n    format => $rng_fmt,\n    optional => 1,\n    description => \"Configure a VirtIO-based Random Number Generator.\",\n};\nPVE::JSONSchema::register_standard_option('pve-qm-rng', $rngdesc);\n\nsub parse_rng {\n    my ($value) = @_;\n\n    return if !$value;\n\n    my $res = eval { PVE::JSONSchema::parse_property_string($rng_fmt, $value) };\n    warn $@ if $@;\n\n    return $res;\n}\n\nsub check_rng_source {\n    my ($source) = @_;\n\n    # mostly relevant for /dev/hwrng, but doesn't hurt to check others too\n    die \"cannot create VirtIO RNG device: source file '$source' doesn't exist\\n\"\n        if !-e $source;\n\n    my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';\n    if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {\n        # Needs to abort, otherwise QEMU crashes on first rng access. Note that rng_current cannot\n        # be changed to 'none' manually, so once the VM is past this point, it's no longer an issue.\n        die \"Cannot start VM with passed-through RNG device: '/dev/hwrng' exists, but\"\n            . \" '$rng_current' is set to 'none'. Ensure that a compatible hardware-RNG is attached\"\n            . \" to the host.\\n\";\n    }\n}\n\nsub print_rng_device_commandline {\n    my ($id, $rng, $bridges, $arch) = @_;\n\n    die \"no rng device specified\\n\" if !$rng;\n\n    my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};\n    my $period = $rng->{period} // $rng_fmt->{period}->{default};\n    my $limiter_str = \"\";\n    if ($max_bytes) {\n        $limiter_str = \",max-bytes=$max_bytes,period=$period\";\n    }\n\n    my $rng_addr = print_pci_addr($id, $bridges, $arch);\n\n    return \"virtio-rng-pci,rng=$id$limiter_str$rng_addr\";\n}\n\nsub print_rng_object_commandline {\n    my ($id, $rng) = @_;\n\n    die \"no rng device specified\\n\" if !$rng;\n\n    my $source_path = $rng->{source};\n    check_rng_source($source_path);\n\n    return \"rng-random,filename=$source_path,id=$id\";\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/RunState.pm",
    "content": "package PVE::QemuServer::RunState;\n\nuse strict;\nuse warnings;\n\nuse POSIX qw(strftime);\n\nuse PVE::Cluster;\nuse PVE::RPCEnvironment;\nuse PVE::Storage;\n\nuse PVE::QemuConfig;\nuse PVE::QemuMigrate::Helpers;\nuse PVE::QemuServer::Monitor qw(mon_cmd);\nuse PVE::QemuServer::Network;\n\n# note: if using the statestorage parameter, the caller has to check privileges\nsub vm_suspend {\n    my ($vmid, $skiplock, $includestate, $statestorage) = @_;\n\n    my $conf;\n    my $path;\n    my $storecfg;\n    my $vmstate;\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n\n            $conf = PVE::QemuConfig->load_config($vmid);\n\n            my $is_backing_up = PVE::QemuConfig->has_lock($conf, 'backup');\n            PVE::QemuConfig->check_lock($conf)\n                if !($skiplock || $is_backing_up);\n\n            die \"cannot suspend to disk during backup\\n\"\n                if $is_backing_up && $includestate;\n\n            PVE::QemuMigrate::Helpers::check_non_migratable_resources($conf, $includestate, 0);\n\n            if ($includestate) {\n                $conf->{lock} = 'suspending';\n                my $date = strftime(\"%Y-%m-%d\", localtime(time()));\n                $storecfg = PVE::Storage::config();\n                if (!$statestorage) {\n                    $statestorage = PVE::QemuConfig::find_vmstate_storage($conf, $storecfg);\n                    # check permissions for the storage\n                    my $rpcenv = PVE::RPCEnvironment::get();\n                    if ($rpcenv->{type} ne 'cli') {\n                        my $authuser = $rpcenv->get_user();\n                        $rpcenv->check(\n                            $authuser,\n                            \"/storage/$statestorage\",\n                            ['Datastore.AllocateSpace'],\n                        );\n                    }\n                }\n\n                $vmstate = PVE::QemuConfig->__snapshot_save_vmstate(\n                    $vmid, $conf, \"suspend-$date\", $storecfg, $statestorage, 1,\n                );\n                $path = PVE::Storage::path($storecfg, $vmstate);\n                PVE::QemuConfig->write_config($vmid, $conf);\n            } else {\n                mon_cmd($vmid, \"stop\");\n            }\n        },\n    );\n\n    if ($includestate) {\n        # save vm state\n        PVE::Storage::activate_volumes($storecfg, [$vmstate]);\n\n        eval {\n            PVE::QemuMigrate::Helpers::set_migration_caps($vmid, 1);\n            mon_cmd($vmid, \"savevm-start\", statefile => $path);\n            for (;;) {\n                my $state = mon_cmd($vmid, \"query-savevm\");\n                if (!$state->{status}) {\n                    die \"savevm not active\\n\";\n                } elsif ($state->{status} eq 'active') {\n                    sleep(1);\n                    next;\n                } elsif ($state->{status} eq 'completed') {\n                    print \"State saved, quitting\\n\";\n                    last;\n                } elsif ($state->{status} eq 'failed' && $state->{error}) {\n                    die \"query-savevm failed with error '$state->{error}'\\n\";\n                } else {\n                    die \"query-savevm returned status '$state->{status}'\\n\";\n                }\n            }\n        };\n        my $err = $@;\n\n        PVE::QemuConfig->lock_config(\n            $vmid,\n            sub {\n                $conf = PVE::QemuConfig->load_config($vmid);\n                if ($err) {\n                    # cleanup, but leave suspending lock, to indicate something went wrong\n                    eval {\n                        eval { mon_cmd($vmid, \"savevm-end\"); };\n                        warn $@ if $@;\n                        PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);\n                        PVE::Storage::vdisk_free($storecfg, $vmstate);\n                        delete $conf->@{\n                            qw(vmstate runningmachine runningcpu running-nets-host-mtu)};\n                        PVE::QemuConfig->write_config($vmid, $conf);\n                    };\n                    warn $@ if $@;\n                    die $err;\n                }\n\n                die \"lock changed unexpectedly\\n\"\n                    if !PVE::QemuConfig->has_lock($conf, 'suspending');\n\n                mon_cmd($vmid, \"quit\");\n                $conf->{lock} = 'suspended';\n                PVE::QemuConfig->write_config($vmid, $conf);\n            },\n        );\n    }\n}\n\n# $nocheck is set when called as part of a migration - in this context the\n# location of the config file (source or target node) is not deterministic,\n# since migration cannot wait for pmxcfs to process the rename\nsub vm_resume {\n    my ($vmid, $skiplock, $nocheck) = @_;\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n            # After migration, the VM might not immediately be able to respond to QMP commands, because\n            # activating the block devices might take a bit of time.\n            my $res = mon_cmd($vmid, 'query-status', timeout => 60);\n            my $resume_cmd = 'cont';\n            my $reset = 0;\n            my $conf;\n            if ($nocheck) {\n                $conf = eval { PVE::QemuConfig->load_config($vmid) }; # try on target node\n                if ($@) {\n                    my $vmlist = PVE::Cluster::get_vmlist();\n                    if (exists($vmlist->{ids}->{$vmid})) {\n                        my $node = $vmlist->{ids}->{$vmid}->{node};\n                        $conf = eval { PVE::QemuConfig->load_config($vmid, $node) }; # try on source node\n                    }\n                    if (!$conf) {\n                        PVE::Cluster::cfs_update(); # vmlist was wrong, invalidate cache\n                        $conf = PVE::QemuConfig->load_config($vmid); # last try on target node again\n                    }\n                }\n            } else {\n                $conf = PVE::QemuConfig->load_config($vmid);\n            }\n\n            die \"VM $vmid is a template and cannot be resumed!\\n\"\n                if PVE::QemuConfig->is_template($conf);\n\n            if ($res->{status}) {\n                return if $res->{status} eq 'running'; # job done, go home\n                $resume_cmd = 'system_wakeup' if $res->{status} eq 'suspended';\n                $reset = 1 if $res->{status} eq 'shutdown';\n            }\n\n            if (!$nocheck) {\n                PVE::QemuConfig->check_lock($conf)\n                    if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));\n            }\n\n            if ($reset) {\n                # required if a VM shuts down during a backup and we get a resume\n                # request before the backup finishes for example\n                mon_cmd($vmid, \"system_reset\");\n            }\n\n            PVE::QemuServer::Network::add_nets_bridge_fdb($conf, $vmid)\n                if $resume_cmd eq 'cont';\n\n            mon_cmd($vmid, $resume_cmd);\n        },\n    );\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/StateFile.pm",
    "content": "package PVE::QemuServer::StateFile;\n\nuse strict;\nuse warnings;\n\nuse PVE::Cluster;\nuse PVE::Network;\n\nsub get_migration_ip {\n    my ($nodename, $cidr) = @_;\n\n    if (!defined($cidr)) {\n        my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg');\n        $cidr = $dc_conf->{migration}->{network};\n    }\n\n    if (defined($cidr)) {\n        my $ips = PVE::Network::get_local_ip_from_cidr($cidr);\n\n        die \"could not get IP: no address configured on local node for network '$cidr'\\n\"\n            if scalar(@$ips) == 0;\n\n        die \"could not get IP: multiple addresses configured on local node for network '$cidr'\\n\"\n            if scalar(@$ips) > 1;\n\n        return $ips->[0];\n    }\n\n    return PVE::Cluster::remote_node_ip($nodename, 1);\n}\n\n# $migration_ip must be defined if using insecure TCP migration\nsub statefile_cmdline_option {\n    my ($storecfg, $vmid, $statefile, $migration_type, $migration_ip) = @_;\n\n    my $statefile_is_a_volume = 0;\n    my $res = {};\n    my $cmd = [];\n\n    if ($statefile eq 'tcp') {\n        my $migrate = $res->{migrate} = { proto => 'tcp' };\n        $migrate->{addr} = \"localhost\";\n\n        die \"no migration type set\\n\" if !defined($migration_type);\n\n        if ($migration_type eq 'insecure') {\n            $migrate->{addr} = $migration_ip // die \"internal error - no migration IP\";\n            $migrate->{addr} = \"[$migrate->{addr}]\" if Net::IP::ip_is_ipv6($migrate->{addr});\n        }\n\n        # see #4501: port reservation should be done close to usage - tell QEMU where to listen\n        # via QMP later\n        push @$cmd, '-incoming', 'defer';\n        push @$cmd, '-S';\n\n    } elsif ($statefile eq 'unix') {\n        # should be default for secure migrations as a ssh TCP forward\n        # tunnel is not deterministic reliable ready and fails regurarly\n        # to set up in time, so use UNIX socket forwards\n        my $migrate = $res->{migrate} = { proto => 'unix' };\n        $migrate->{addr} = \"/run/qemu-server/$vmid.migrate\";\n        unlink $migrate->{addr};\n\n        $migrate->{uri} = \"unix:$migrate->{addr}\";\n        push @$cmd, '-incoming', $migrate->{uri};\n        push @$cmd, '-S';\n\n    } elsif (-e $statefile) {\n        push @$cmd, '-loadstate', $statefile;\n    } else {\n        my $statepath = PVE::Storage::path($storecfg, $statefile);\n        $statefile_is_a_volume = 1;\n        push @$cmd, '-loadstate', $statepath;\n    }\n\n    return ($cmd, $res->{migrate}, $statefile_is_a_volume);\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/USB.pm",
    "content": "package PVE::QemuServer::USB;\n\nuse strict;\nuse warnings;\nuse PVE::QemuServer::PCI qw(print_pci_addr);\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Helpers qw(min_version windows_version);\nuse PVE::JSONSchema;\nuse PVE::Mapping::USB;\nuse base 'Exporter';\n\nour @EXPORT_OK = qw(\n    parse_usb_device\n    get_usb_controllers\n    get_usb_devices\n);\n\nmy $OLD_MAX_USB = 5;\nour $MAX_USB_DEVICES = 14;\n\nmy $USB_ID_RE = qr/(0x)?([0-9A-Fa-f]{4}):(0x)?([0-9A-Fa-f]{4})/;\nmy $USB_PATH_RE = qr/(\\d+)\\-(\\d+(\\.\\d+)*)/;\n\nmy $usb_fmt = {\n    host => {\n        default_key => 1,\n        optional => 1,\n        type => 'string',\n        pattern => qr/(?:(?:$USB_ID_RE)|(?:$USB_PATH_RE)|[Ss][Pp][Ii][Cc][Ee])/,\n        format_description => 'HOSTUSBDEVICE|spice',\n        description => <<EODESCR,\nThe Host USB device or port or the value 'spice'. HOSTUSBDEVICE syntax is:\n\n 'bus-port(.port)*' (decimal numbers) or\n 'vendor_id:product_id' (hexadecimal numbers) or\n 'spice'\n\nYou can use the 'lsusb -t' command to list existing usb devices.\n\nNOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such\nmachines - use with special care.\n\nThe value 'spice' can be used to add a usb redirection devices for spice.\n\nEither this or the 'mapping' key must be set.\nEODESCR\n    },\n    mapping => {\n        optional => 1,\n        type => 'string',\n        format_description => 'mapping-id',\n        format => 'pve-configid',\n        description => \"The ID of a cluster wide mapping. Either this or the default-key 'host'\"\n            . \" must be set.\",\n    },\n    usb3 => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Specifies whether if given host option is a USB3 device or port.\"\n            . \" For modern guests (machine version >= 7.1 and ostype l26 and windows > 7), this flag\"\n            . \" is irrelevant (all devices are plugged into a xhci controller).\",\n        default => 0,\n    },\n};\n\nPVE::JSONSchema::register_format('pve-qm-usb', $usb_fmt);\n\nour $usbdesc = {\n    optional => 1,\n    type => 'string',\n    format => $usb_fmt,\n    description => \"Configure an USB device (n is 0 to 4, for machine version >= 7.1 and ostype\"\n        . \" l26 or windows > 7, n can be up to 14).\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-usb\", $usbdesc);\n\nsub parse_usb_device {\n    my ($value, $mapping) = @_;\n\n    return if $value && $mapping; # not a valid configuration\n\n    my $res = {};\n    if (defined($value)) {\n        if ($value =~ m/^$USB_ID_RE$/) {\n            $res->{vendorid} = $2;\n            $res->{productid} = $4;\n        } elsif ($value =~ m/^$USB_PATH_RE$/) {\n            $res->{hostbus} = $1;\n            $res->{hostport} = $2;\n        } elsif ($value =~ m/^spice$/i) {\n            $res->{spice} = 1;\n        }\n    } elsif (defined($mapping)) {\n        my $devices = PVE::Mapping::USB::find_on_current_node($mapping);\n        die \"USB device mapping not found for '$mapping'\\n\" if !$devices || !scalar($devices->@*);\n        die \"More than one USB mapping per host not supported\\n\" if scalar($devices->@*) > 1;\n        eval { PVE::Mapping::USB::assert_valid($mapping, $devices->[0]); };\n        if (my $err = $@) {\n            die \"USB Mapping invalid (hardware probably changed): $err\\n\";\n        }\n        my $device = $devices->[0];\n\n        if ($device->{path}) {\n            $res = parse_usb_device($device->{path});\n        } else {\n            $res = parse_usb_device($device->{id});\n        }\n    }\n\n    return $res;\n}\n\nmy sub assert_usb_index_is_useable {\n    my ($index, $use_qemu_xhci) = @_;\n\n    die \"using usb$index is only possible with machine type >= 7.1 and ostype l26 or windows > 7\\n\"\n        if $index >= $OLD_MAX_USB && !$use_qemu_xhci;\n\n    return undef;\n}\n\nsub get_usb_controllers {\n    my ($conf, $bridges, $arch, $machine_version) = @_;\n\n    my $devices = [];\n    my $pciaddr = \"\";\n\n    my $ostype = $conf->{ostype};\n\n    my $use_qemu_xhci =\n        min_version($machine_version, 7, 1)\n        && defined($ostype)\n        && ($ostype eq 'l26' || windows_version($ostype) > 7);\n    my $is_q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n\n    if ($arch eq 'aarch64') {\n        $pciaddr = print_pci_addr('ehci', $bridges, $arch);\n        push @$devices, '-device', \"usb-ehci,id=ehci$pciaddr\";\n    } elsif (!$is_q35) {\n        $pciaddr = print_pci_addr(\"piix3\", $bridges, $arch);\n        push @$devices, '-device', \"piix3-usb-uhci,id=uhci$pciaddr.0x2\";\n    }\n\n    my ($use_usb2, $use_usb3) = 0;\n    my $any_usb = 0;\n    for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) {\n        next if !$conf->{\"usb$i\"};\n        assert_usb_index_is_useable($i, $use_qemu_xhci);\n        my $d = eval { PVE::JSONSchema::parse_property_string($usb_fmt, $conf->{\"usb$i\"}) } or next;\n        $any_usb = 1;\n        $use_usb3 = 1 if $d->{usb3};\n        $use_usb2 = 1 if !$d->{usb3};\n    }\n\n    if (!$use_qemu_xhci && !$is_q35 && $use_usb2 && $arch ne 'aarch64') {\n        # include usb device config if still on x86 before-xhci machines and if USB 3 is not used\n        push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg';\n    }\n\n    $pciaddr = print_pci_addr(\"xhci\", $bridges, $arch);\n    if ($use_qemu_xhci && $any_usb) {\n        push @$devices, '-device', print_qemu_xhci_controller($pciaddr);\n    } elsif ($use_usb3) {\n        push @$devices, '-device', \"nec-usb-xhci,id=xhci$pciaddr\";\n    }\n\n    return @$devices;\n}\n\nsub get_usb_devices {\n    my ($conf, $features, $bootorder, $machine_version) = @_;\n\n    my $devices = [];\n\n    my $ostype = $conf->{ostype};\n    my $use_qemu_xhci =\n        min_version($machine_version, 7, 1)\n        && defined($ostype)\n        && ($ostype eq 'l26' || windows_version($ostype) > 7);\n\n    for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) {\n        my $devname = \"usb$i\";\n        next if !$conf->{$devname};\n        assert_usb_index_is_useable($i, $use_qemu_xhci);\n        my $d = eval { PVE::JSONSchema::parse_property_string($usb_fmt, $conf->{$devname}) };\n        next if !$d;\n\n        my $port = $use_qemu_xhci ? $i + 1 : undef;\n\n        if ($d->{host} && $d->{host} =~ m/^spice$/) {\n            # usb redir support for spice\n            my $bus = 'ehci';\n            $bus = 'xhci' if ($d->{usb3} && $features->{spice_usb3}) || $use_qemu_xhci;\n\n            push @$devices, '-chardev', \"spicevmc,id=usbredirchardev$i,name=usbredir\";\n            push @$devices, '-device', print_spice_usbdevice($i, $bus, $port);\n\n            warn \"warning: spice usb port set as bootdevice, ignoring\\n\" if $bootorder->{$devname};\n        } else {\n            push @$devices, '-device', print_usbdevice_full($conf, $devname, $d, $bootorder, $port);\n        }\n    }\n\n    return @$devices;\n}\n\nsub print_qemu_xhci_controller {\n    my ($pciaddr) = @_;\n    return \"qemu-xhci,p2=15,p3=15,id=xhci$pciaddr\";\n}\n\nsub print_spice_usbdevice {\n    my ($index, $bus, $port) = @_;\n    my $device = \"usb-redir,chardev=usbredirchardev$index,id=usbredirdev$index,bus=$bus.0\";\n    if (defined($port)) {\n        $device .= \",port=$port\";\n    }\n    return $device;\n}\n\nsub print_usbdevice_full {\n    my ($conf, $deviceid, $device, $bootorder, $port) = @_;\n\n    return if !$device;\n    my $usbdevice = \"usb-host\";\n\n    # if it is a usb3 device or with newer qemu, attach it to the xhci controller, else omit the bus option\n    if ($device->{usb3} || defined($port)) {\n        $usbdevice .= \",bus=xhci.0\";\n        $usbdevice .= \",port=$port\" if defined($port);\n    }\n\n    my $parsed = parse_usb_device($device->{host}, $device->{mapping});\n\n    if (defined($parsed->{vendorid}) && defined($parsed->{productid})) {\n        $usbdevice .= \",vendorid=0x$parsed->{vendorid},productid=0x$parsed->{productid}\";\n    } elsif (defined($parsed->{hostbus}) && defined($parsed->{hostport})) {\n        $usbdevice .= \",hostbus=$parsed->{hostbus},hostport=$parsed->{hostport}\";\n    } else {\n        die \"no usb id or path given\\n\";\n    }\n\n    $usbdevice .= \",id=$deviceid\";\n    $usbdevice .= \",bootindex=$bootorder->{$deviceid}\" if $bootorder->{$deviceid};\n    return $usbdevice;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/Virtiofs.pm",
    "content": "package PVE::QemuServer::Virtiofs;\n\nuse strict;\nuse warnings;\n\nuse Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);\nuse IO::Socket::UNIX;\nuse POSIX;\nuse Socket qw(SOCK_STREAM);\n\nuse PVE::JSONSchema qw(parse_property_string);\nuse PVE::Mapping::Dir;\nuse PVE::QemuServer::Helpers;\nuse PVE::RESTEnvironment qw(log_warn);\n\nuse base qw(Exporter);\n\nour @EXPORT_OK = qw(\n    max_virtiofs\n    start_all_virtiofsd\n);\n\nmy $MAX_VIRTIOFS = 10;\nmy $socket_path_root = \"/run/qemu-server/virtiofsd\";\n\nmy $virtiofs_fmt = {\n    'dirid' => {\n        type => 'string',\n        default_key => 1,\n        description =>\n            \"Mapping identifier of the directory mapping to be shared with the guest.\"\n            . \" Also used as a mount tag inside the VM.\",\n        format_description => 'mapping-id',\n        format => 'pve-configid',\n    },\n    'cache' => {\n        type => 'string',\n        description =>\n            \"The caching policy the file system should use (auto, always, metadata, never).\",\n        enum => [qw(auto always metadata never)],\n        default => \"auto\",\n        optional => 1,\n    },\n    'direct-io' => {\n        type => 'boolean',\n        description => \"Honor the O_DIRECT flag passed down by guest applications.\",\n        default => 0,\n        optional => 1,\n    },\n    'expose-xattr' => {\n        type => 'boolean',\n        description => \"Enable support for extended attributes for this mount.\",\n        default => 0,\n        optional => 1,\n    },\n    'expose-acl' => {\n        type => 'boolean',\n        description =>\n            \"Enable support for POSIX ACLs (enabled ACL implies xattr) for this mount.\",\n        default => 0,\n        optional => 1,\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-virtiofs', $virtiofs_fmt);\n\nmy $virtiofsdesc = {\n    optional => 1,\n    type => 'string',\n    format => $virtiofs_fmt,\n    description =>\n        \"Configuration for sharing a directory between host and guest using Virtio-fs.\",\n};\nPVE::JSONSchema::register_standard_option(\"pve-qm-virtiofs\", $virtiofsdesc);\n\nsub max_virtiofs {\n    return $MAX_VIRTIOFS;\n}\n\nsub assert_virtiofs_config {\n    my ($ostype, $virtiofs) = @_;\n\n    my $dir_cfg = PVE::Mapping::Dir::find_on_current_node($virtiofs->{dirid});\n\n    my $acl = $virtiofs->{'expose-acl'};\n    if ($acl && PVE::QemuServer::Helpers::windows_version($ostype)) {\n        die \"Please disable ACLs for virtiofs on Windows VMs, otherwise\"\n            . \" the virtiofs shared directory cannot be mounted.\\n\";\n    }\n\n    eval { PVE::Mapping::Dir::assert_valid($dir_cfg) };\n    die \"directory mapping invalid: $@\\n\" if $@;\n}\n\nsub config {\n    my ($conf, $vmid, $devices) = @_;\n\n    for (my $i = 0; $i < max_virtiofs(); $i++) {\n        my $opt = \"virtiofs$i\";\n\n        next if !$conf->{$opt};\n        my $virtiofs = parse_property_string('pve-qm-virtiofs', $conf->{$opt});\n\n        assert_virtiofs_config($conf->{ostype}, $virtiofs);\n\n        push @$devices, '-chardev', \"socket,id=virtiofs$i,path=$socket_path_root/vm$vmid-fs$i\";\n\n        # queue-size is set 1024 because of bug with Windows guests:\n        # https://bugzilla.redhat.com/show_bug.cgi?id=1873088\n        # 1024 is also always used in the virtiofs documentations:\n        # https://gitlab.com/virtio-fs/virtiofsd#examples\n        push @$devices, '-device',\n            'vhost-user-fs-pci,queue-size=1024' . \",chardev=virtiofs$i,tag=$virtiofs->{dirid}\";\n    }\n}\n\nsub virtiofs_enabled {\n    my ($conf) = @_;\n\n    my $virtiofs_enabled = 0;\n    for (my $i = 0; $i < max_virtiofs(); $i++) {\n        my $opt = \"virtiofs$i\";\n        next if !$conf->{$opt};\n        parse_property_string('pve-qm-virtiofs', $conf->{$opt});\n        $virtiofs_enabled = 1;\n    }\n    return $virtiofs_enabled;\n}\n\nsub start_all_virtiofsd {\n    my ($conf, $vmid) = @_;\n    my $virtiofs_sockets = [];\n    for (my $i = 0; $i < max_virtiofs(); $i++) {\n        my $opt = \"virtiofs$i\";\n\n        next if !$conf->{$opt};\n        my $virtiofs = parse_property_string('pve-qm-virtiofs', $conf->{$opt});\n\n        # See https://github.com/virtio-win/kvm-guest-drivers-windows/issues/1136\n        my $prefer_inode_fh = PVE::QemuServer::Helpers::windows_version($conf->{ostype}) ? 1 : 0;\n\n        my $virtiofs_socket = start_virtiofsd($vmid, $i, $virtiofs, $prefer_inode_fh);\n        push @$virtiofs_sockets, $virtiofs_socket;\n    }\n    return $virtiofs_sockets;\n}\n\nsub start_virtiofsd {\n    my ($vmid, $fsid, $virtiofs, $prefer_inode_fh) = @_;\n\n    mkdir $socket_path_root;\n    my $socket_path = \"$socket_path_root/vm$vmid-fs$fsid\";\n    unlink($socket_path);\n    my $socket = IO::Socket::UNIX->new(\n        Type => SOCK_STREAM,\n        Local => $socket_path,\n        Listen => 1,\n    ) or die \"cannot create socket - $!\\n\";\n\n    my $flags = fcntl($socket, F_GETFD, 0)\n        or die \"failed to get file descriptor flags: $!\\n\";\n    fcntl($socket, F_SETFD, $flags & ~FD_CLOEXEC)\n        or die \"failed to remove FD_CLOEXEC from file descriptor\\n\";\n\n    my $dir_cfg = PVE::Mapping::Dir::find_on_current_node($virtiofs->{dirid});\n\n    my $virtiofsd_bin = '/usr/libexec/virtiofsd';\n    if (!-f $virtiofsd_bin) {\n        die \"virtiofsd is not installed. To use virtio-fs, install virtiofsd via apt.\\n\";\n    }\n    my $fd = $socket->fileno();\n    my $path = $dir_cfg->{path};\n\n    my $could_not_fork_err = \"could not fork to start virtiofsd\\n\";\n    my $pid = fork();\n    if ($pid == 0) {\n        POSIX::setsid();\n        $0 = \"task pve-vm$vmid-virtiofs$fsid\";\n        my $pid2 = fork();\n        if ($pid2 == 0) {\n            my $cmd = [$virtiofsd_bin, \"--fd=$fd\", \"--shared-dir=$path\"];\n            push @$cmd, '--xattr' if $virtiofs->{'expose-xattr'};\n            push @$cmd, '--posix-acl' if $virtiofs->{'expose-acl'};\n            push @$cmd, '--announce-submounts';\n            push @$cmd, '--allow-direct-io' if $virtiofs->{'direct-io'};\n            push @$cmd, '--cache=' . $virtiofs->{cache} if $virtiofs->{cache};\n            push @$cmd, '--inode-file-handles=prefer' if $prefer_inode_fh;\n            push @$cmd, '--syslog';\n            exec(@$cmd);\n        } elsif (!defined($pid2)) {\n            die $could_not_fork_err;\n        } else {\n            POSIX::_exit(0);\n        }\n    } elsif (!defined($pid)) {\n        die $could_not_fork_err;\n    } else {\n        waitpid($pid, 0);\n    }\n\n    # return socket to keep it alive,\n    # so that QEMU will wait for virtiofsd to start\n    return $socket;\n}\n\nsub close_sockets {\n    my @sockets = @_;\n    for my $socket (@sockets) {\n        shutdown($socket, 2);\n        close($socket);\n    }\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer/VolumeChain.pm",
    "content": "package PVE::QemuServer::VolumeChain;\n\nuse strict;\nuse warnings;\n\nuse File::Basename qw(basename dirname);\nuse JSON;\n\nuse PVE::Storage;\n\nuse PVE::QemuServer::Blockdev qw(generate_file_blockdev generate_format_blockdev);\nuse PVE::QemuServer::BlockJob;\nuse PVE::QemuServer::Drive;\nuse PVE::QemuServer::Monitor qw(qmp_cmd);\n\nsub blockdev_external_snapshot {\n    my ($storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;\n\n    print \"Creating a new current volume with $snap as backing snap\\n\";\n\n    my $volid = $drive->{file};\n\n    #rename current to snap && preallocate add a new current file with reference to snap1 backing-file\n    PVE::Storage::volume_snapshot($storecfg, $volid, $snap);\n\n    #reopen current to snap\n    blockdev_replace(\n        $storecfg,\n        $qmp_peer,\n        $machine_version,\n        $deviceid,\n        $drive,\n        'current',\n        $snap,\n        $parent_snap,\n    );\n\n    #be sure to add drive in write mode\n    delete($drive->{ro});\n\n    my $new_file_blockdev = generate_file_blockdev($storecfg, $drive);\n    my $new_fmt_blockdev = generate_format_blockdev($storecfg, $drive, $new_file_blockdev);\n\n    my $snap_file_blockdev =\n        generate_file_blockdev($storecfg, $drive, $machine_version, { 'snapshot-name' => $snap });\n    my $snap_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $snap_file_blockdev,\n        { 'snapshot-name' => $snap },\n    );\n\n    #backing need to be forced to undef in blockdev, to avoid reopen of backing-file on blockdev-add\n    $new_fmt_blockdev->{backing} = undef;\n\n    qmp_cmd($qmp_peer, 'blockdev-add', %$new_fmt_blockdev);\n\n    print \"blockdev-snapshot: reopen current with $snap backing image\\n\";\n    qmp_cmd(\n        $qmp_peer, 'blockdev-snapshot',\n        node => $snap_fmt_blockdev->{'node-name'},\n        overlay => $new_fmt_blockdev->{'node-name'},\n    );\n}\n\nsub blockdev_delete {\n    my ($storecfg, $qmp_peer, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;\n\n    eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $fmt_blockdev->{'node-name'}); };\n    warn \"detaching block node for $file_blockdev->{filename} failed - $@\" if $@;\n\n    #delete the file (don't use vdisk_free as we don't want to delete all snapshot chain)\n    print \"delete old $file_blockdev->{filename}\\n\";\n\n    my $storage_name = PVE::Storage::parse_volume_id($drive->{file});\n\n    my $volid = $drive->{file};\n    PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, 1);\n}\n\nmy sub blockdev_relative_backing_file {\n    my ($backing, $backed) = @_;\n\n    my $backing_file = $backing->{filename};\n    my $backed_file = $backed->{filename};\n\n    if (dirname($backing_file) eq dirname($backed_file)) {\n        # make backing file relative if in same directory\n        return basename($backing_file);\n    }\n\n    return $backing_file;\n}\n\nsub blockdev_replace {\n    my (\n        $storecfg,\n        $qmp_peer,\n        $machine_version,\n        $deviceid,\n        $drive,\n        $src_snap,\n        $target_snap,\n        $parent_snap,\n    ) = @_;\n\n    print \"blockdev replace $src_snap by $target_snap\\n\";\n\n    my $volid = $drive->{file};\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n\n    my $src_name_options = {};\n    my $src_blockdev_name;\n    if ($src_snap eq 'current') {\n        # there might be other nodes on top like zeroinit, look up the current node below throttle\n        $src_blockdev_name =\n            PVE::QemuServer::Blockdev::get_node_name_below_throttle($qmp_peer, $deviceid, 1);\n    } else {\n        $src_name_options = { 'snapshot-name' => $src_snap };\n        $src_blockdev_name =\n            PVE::QemuServer::Blockdev::get_node_name('fmt', $drive_id, $volid, $src_name_options);\n    }\n\n    my $target_file_blockdev = generate_file_blockdev(\n        $storecfg,\n        $drive,\n        $machine_version,\n        { 'snapshot-name' => $target_snap },\n    );\n    my $target_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $target_file_blockdev,\n        { 'snapshot-name' => $target_snap },\n    );\n\n    if ($target_snap eq 'current' || $src_snap eq 'current') {\n        #rename from|to current\n\n        #add backing to target\n        if ($parent_snap) {\n            my $parent_fmt_nodename = PVE::QemuServer::Blockdev::get_node_name(\n                'fmt',\n                $drive_id,\n                $volid,\n                { 'snapshot-name' => $parent_snap },\n            );\n            $target_fmt_blockdev->{backing} = $parent_fmt_nodename;\n        }\n        qmp_cmd($qmp_peer, 'blockdev-add', %$target_fmt_blockdev);\n\n        #reopen the current throttlefilter nodename with the target fmt nodename\n        my $throttle_blockdev = PVE::QemuServer::Blockdev::generate_throttle_blockdev(\n            $drive, $target_fmt_blockdev->{'node-name'}, {},\n        );\n        qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$throttle_blockdev]);\n    } else {\n        #intermediate snapshot\n        qmp_cmd($qmp_peer, 'blockdev-add', %$target_fmt_blockdev);\n\n        #reopen the parent node with the new target fmt backing node\n        my $parent_file_blockdev = generate_file_blockdev(\n            $storecfg,\n            $drive,\n            $machine_version,\n            { 'snapshot-name' => $parent_snap },\n        );\n        my $parent_fmt_blockdev = generate_format_blockdev(\n            $storecfg,\n            $drive,\n            $parent_file_blockdev,\n            { 'snapshot-name' => $parent_snap },\n        );\n        $parent_fmt_blockdev->{backing} = $target_fmt_blockdev->{'node-name'};\n        qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$parent_fmt_blockdev]);\n\n        my $backing_file =\n            blockdev_relative_backing_file($target_file_blockdev, $parent_file_blockdev);\n\n        #change backing-file in qcow2 metadatas\n        qmp_cmd(\n            $qmp_peer, 'change-backing-file',\n            device => $deviceid,\n            'image-node-name' => $parent_fmt_blockdev->{'node-name'},\n            'backing-file' => $backing_file,\n        );\n    }\n\n    # delete old file|fmt nodes\n    eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $src_blockdev_name); };\n    warn \"detaching block node for $src_snap failed - $@\" if $@;\n}\n\nsub blockdev_commit {\n    my ($storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $src_snap, $target_snap) = @_;\n\n    my $volid = $drive->{file};\n    my $target_was_read_only;\n\n    print \"block-commit $src_snap to base:$target_snap\\n\";\n\n    my $target_file_blockdev = generate_file_blockdev(\n        $storecfg,\n        $drive,\n        $machine_version,\n        { 'snapshot-name' => $target_snap },\n    );\n    my $target_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $target_file_blockdev,\n        { 'snapshot-name' => $target_snap },\n    );\n\n    my $src_file_blockdev = generate_file_blockdev(\n        $storecfg,\n        $drive,\n        $machine_version,\n        { 'snapshot-name' => $src_snap },\n    );\n    my $src_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $src_file_blockdev,\n        { 'snapshot-name' => $src_snap },\n    );\n\n    if ($target_was_read_only = $target_fmt_blockdev->{'read-only'}) {\n        print \"reopening internal read-only block node for '$target_snap' as writable\\n\";\n        $target_fmt_blockdev->{'read-only'} = JSON::false;\n        $target_file_blockdev->{'read-only'} = JSON::false;\n        qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$target_fmt_blockdev]);\n        # For the guest, the drive is still read-only, because the top throttle node is.\n    }\n\n    eval {\n        my $job_id = \"commit-$deviceid\";\n        my $jobs = {};\n        my $opts = { 'job-id' => $job_id, device => $deviceid };\n\n        $opts->{'base-node'} = $target_fmt_blockdev->{'node-name'};\n        $opts->{'top-node'} = $src_fmt_blockdev->{'node-name'};\n\n        qmp_cmd($qmp_peer, \"block-commit\", %$opts);\n        $jobs->{$job_id} = {};\n\n        # If the 'current' state is committed to its backing snapshot, the job will not complete\n        # automatically, because there is a writer, i.e. the guest. It is necessary to use the\n        # 'complete' completion mode, so that the 'current' block node is replaced with the backing\n        # node upon completion. Like that, IO after the commit operation will already land in the\n        # backing node, which will be renamed since it will be the new top of the chain (done by the\n        # caller).\n        #\n        # For other snapshots in the chain, it can be assumed that they have no writer, so\n        # 'block-commit' will complete automatically.\n        my $complete = $src_snap && $src_snap ne 'current' ? 'auto' : 'complete';\n\n        PVE::QemuServer::BlockJob::monitor($qmp_peer, undef, $jobs, $complete, 0, 'commit');\n\n        blockdev_delete(\n            $storecfg, $qmp_peer, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,\n        );\n    };\n    my $err = $@;\n\n    if ($target_was_read_only) {\n        # Even when restoring the read-only flag on the format and file nodes fails, the top\n        # throttle node still has it, ensuring it is read-only for the guest.\n        print \"re-applying read-only flag for internal block node for '$target_snap'\\n\";\n        $target_fmt_blockdev->{'read-only'} = JSON::true;\n        $target_file_blockdev->{'read-only'} = JSON::true;\n        eval { qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$target_fmt_blockdev]); };\n        print \"failed to re-apply read-only flag - $@\\n\" if $@;\n    }\n\n    die $err if $err;\n}\n\nsub blockdev_stream {\n    my (\n        $storecfg,\n        $qmp_peer,\n        $machine_version,\n        $deviceid,\n        $drive,\n        $snap,\n        $parent_snap,\n        $target_snap,\n    ) = @_;\n\n    my $volid = $drive->{file};\n    $target_snap = undef if $target_snap eq 'current';\n\n    my $parent_file_blockdev = generate_file_blockdev(\n        $storecfg,\n        $drive,\n        $machine_version,\n        { 'snapshot-name' => $parent_snap },\n    );\n    my $parent_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $parent_file_blockdev,\n        { 'snapshot-name' => $parent_snap },\n    );\n\n    my $target_file_blockdev = generate_file_blockdev(\n        $storecfg,\n        $drive,\n        $machine_version,\n        { 'snapshot-name' => $target_snap },\n    );\n    my $target_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $target_file_blockdev,\n        { 'snapshot-name' => $target_snap },\n    );\n\n    my $snap_file_blockdev =\n        generate_file_blockdev($storecfg, $drive, $machine_version, { 'snapshot-name' => $snap });\n    my $snap_fmt_blockdev = generate_format_blockdev(\n        $storecfg,\n        $drive,\n        $snap_file_blockdev,\n        { 'snapshot-name' => $snap },\n    );\n\n    my $backing_file = blockdev_relative_backing_file($parent_file_blockdev, $target_file_blockdev);\n\n    my $job_id = \"stream-$deviceid\";\n    my $jobs = {};\n    my $options = { 'job-id' => $job_id, device => $target_fmt_blockdev->{'node-name'} };\n    $options->{'base-node'} = $parent_fmt_blockdev->{'node-name'};\n    $options->{'backing-file'} = $backing_file;\n\n    qmp_cmd($qmp_peer, 'block-stream', %$options);\n    $jobs->{$job_id} = {};\n\n    PVE::QemuServer::BlockJob::monitor($qmp_peer, undef, $jobs, 'auto', 0, 'stream');\n\n    blockdev_delete(\n        $storecfg, $qmp_peer, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap,\n    );\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/QemuServer.pm",
    "content": "package PVE::QemuServer;\n\nuse strict;\nuse warnings;\n\nuse Cwd 'abs_path';\nuse Digest::SHA;\nuse Fcntl ':flock';\nuse Fcntl;\nuse File::Basename;\nuse File::Copy qw(copy);\nuse File::Path;\nuse Getopt::Long;\nuse IO::Dir;\nuse IO::File;\nuse IO::Handle;\nuse IO::Select;\nuse IO::Socket::UNIX;\nuse IPC::Open3;\nuse JSON;\nuse MIME::Base64;\nuse POSIX;\nuse Storable qw(dclone);\nuse Time::HiRes qw(gettimeofday usleep);\nuse URI::Escape;\nuse UUID;\n\nuse PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);\nuse PVE::CGroup;\nuse PVE::CpuSet;\nuse PVE::DataCenterConfig;\nuse PVE::Exception qw(raise raise_param_exc);\nuse PVE::Format qw(render_duration render_bytes);\nuse PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);\nuse PVE::Mapping::Dir;\nuse PVE::Mapping::PCI;\nuse PVE::Mapping::USB;\nuse PVE::Network::SDN::Vnets;\nuse PVE::INotify;\nuse PVE::JSONSchema qw(get_standard_option parse_property_string);\nuse PVE::ProcFSTools;\nuse PVE::PBSClient;\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::RPCEnvironment;\nuse PVE::SafeSyslog;\nuse PVE::Storage;\nuse PVE::SysFSTools;\nuse PVE::Systemd;\nuse PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_foreach $IPV6RE);\n\nuse PVE::QMPClient;\nuse PVE::QemuConfig;\nuse PVE::QemuConfig::NoWrite;\nuse PVE::QemuMigrate::Helpers;\nuse PVE::QemuServer::Agent qw(get_qga_key parse_guest_agent qga_check_running);\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::BlockJob;\nuse PVE::QemuServer::Cfg2Cmd;\nuse PVE::QemuServer::Helpers\n    qw(config_aware_timeout get_iscsi_initiator_name get_host_arch min_version kvm_user_version windows_version);\nuse PVE::QemuServer::Cloudinit;\nuse PVE::QemuServer::CGroup;\nuse PVE::QemuServer::CPUConfig qw(\n    print_cpu_device\n    get_cpu_options\n    get_cpu_bitness\n    is_native_arch\n    get_amd_sev_object\n    get_intel_tdx_object\n    get_cvm_type\n);\nuse PVE::QemuServer::Drive qw(\n    is_valid_drivename\n    checked_volume_format\n    drive_is_cloudinit\n    drive_is_cdrom\n    parse_drive\n    print_drive\n    storage_allows_io_uring_default\n);\nuse PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos);\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Memory qw(get_current_memory);\nuse PVE::QemuServer::MetaInfo;\nuse PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);\nuse PVE::QemuServer::Network;\nuse PVE::QemuServer::OVMF;\nuse PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);\nuse PVE::QemuServer::QemuImage;\nuse PVE::QemuServer::QMPHelpers qw(qemu_deviceadd qemu_devicedel qemu_objectadd qemu_objectdel);\nuse PVE::QemuServer::QSD;\nuse PVE::QemuServer::RNG qw(parse_rng print_rng_device_commandline print_rng_object_commandline);\nuse PVE::QemuServer::RunState;\nuse PVE::QemuServer::StateFile;\nuse PVE::QemuServer::USB;\nuse PVE::QemuServer::Virtiofs qw(max_virtiofs start_all_virtiofsd);\nuse PVE::QemuServer::VolumeChain;\nuse PVE::QemuServer::DBusVMState;\n\nmy $have_ha_config;\neval {\n    require PVE::HA::Config;\n    $have_ha_config = 1;\n};\n\nmy sub vm_is_ha_managed {\n    my ($vmid) = @_;\n    die \"cannot check if VM is managed by HA, missing HA Config module!\\n\" if !$have_ha_config;\n    return PVE::HA::Config::vm_is_ha_managed($vmid);\n}\n\nmy $cpuinfo = PVE::ProcFSTools::read_cpuinfo();\n\n# Note about locking: we use flock on the config file protect against concurrent actions.\n# Additionally, we have a 'lock' setting in the config file. This  can be set to 'migrate',\n# 'backup', 'snapshot' or 'rollback'. Most actions are not allowed when such lock is set.\n# But you can ignore this kind of lock with the --skiplock flag.\n\ncfs_register_file(\n    '/qemu-server/', \\&parse_vm_config, \\&write_vm_config,\n);\n\nPVE::JSONSchema::register_standard_option(\n    'pve-qm-stateuri',\n    {\n        description => \"Some command save/restore state from this location.\",\n        type => 'string',\n        maxLength => 128,\n        optional => 1,\n    },\n);\n\n# FIXME: remove in favor of just using the INotify one, it's cached there exactly the same way\nmy $nodename_cache;\n\nsub nodename {\n    $nodename_cache //= PVE::INotify::nodename();\n    return $nodename_cache;\n}\n\nmy $watchdog_fmt = {\n    model => {\n        default_key => 1,\n        type => 'string',\n        enum => [qw(i6300esb ib700)],\n        description => \"Watchdog type to emulate.\",\n        default => 'i6300esb',\n        optional => 1,\n    },\n    action => {\n        type => 'string',\n        enum => [qw(reset shutdown poweroff pause debug none)],\n        description =>\n            \"The action to perform if after activation the guest fails to poll the watchdog in time.\",\n        optional => 1,\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt);\n\nmy $vga_fmt = {\n    type => {\n        description => \"Select the VGA type. Using type 'cirrus' is not recommended.\",\n        type => 'string',\n        default => 'std',\n        optional => 1,\n        default_key => 1,\n        enum => [\n            qw(cirrus qxl qxl2 qxl3 qxl4 none serial0 serial1 serial2 serial3 std virtio virtio-gl vmware)\n        ],\n    },\n    memory => {\n        description => \"Sets the VGA memory (in MiB). Has no effect with serial display.\",\n        type => 'integer',\n        optional => 1,\n        minimum => 4,\n        maximum => 512,\n    },\n    clipboard => {\n        description =>\n            'Enable a specific clipboard. If not set, depending on the display type the SPICE one'\n            . ' will be added. Live migration with a VNC clipboard is not possible with QEMU'\n            . ' machine version < 10.1.',\n        type => 'string',\n        enum => ['vnc'],\n        optional => 1,\n    },\n};\n\nmy $ivshmem_fmt = {\n    size => {\n        type => 'integer',\n        minimum => 1,\n        description => \"The size of the file in MB.\",\n    },\n    name => {\n        type => 'string',\n        pattern => '[a-zA-Z0-9\\-]+',\n        optional => 1,\n        format_description => 'string',\n        description =>\n            \"The name of the file. Will be prefixed with 'pve-shm-'. Default is the VMID. Will be deleted when the VM is stopped.\",\n    },\n};\n\nmy $audio_fmt = {\n    device => {\n        type => 'string',\n        enum => [qw(ich9-intel-hda intel-hda AC97)],\n        description => \"Configure an audio device.\",\n    },\n    driver => {\n        type => 'string',\n        enum => ['spice', 'none'],\n        default => 'spice',\n        optional => 1,\n        description => \"Driver backend for the audio device.\",\n    },\n};\n\nmy $spice_enhancements_fmt = {\n    foldersharing => {\n        type => 'boolean',\n        optional => 1,\n        default => '0',\n        description =>\n            \"Enable folder sharing via SPICE. Needs Spice-WebDAV daemon installed in the VM.\",\n    },\n    videostreaming => {\n        type => 'string',\n        enum => ['off', 'all', 'filter'],\n        default => 'off',\n        optional => 1,\n        description => \"Enable video streaming. Uses compression for detected video streams.\",\n    },\n};\n\nmy $confdesc = {\n    onboot => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Specifies whether a VM will be started during system bootup.\",\n        default => 0,\n    },\n    autostart => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Automatic restart after crash (currently ignored).\",\n        default => 0,\n    },\n    hotplug => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-hotplug-features',\n        description => \"Selectively enable hotplug features. This is a comma separated list of\"\n            . \" hotplug features: 'network', 'disk', 'cpu', 'memory', 'usb' and 'cloudinit'. Use '0' to disable\"\n            . \" hotplug completely. Using '1' as value is an alias for the default `network,disk,usb`.\"\n            . \" USB hotplugging is possible for guests with machine version >= 7.1 and ostype l26 or\"\n            . \" windows > 7.\",\n        default => 'network,disk,usb',\n    },\n    reboot => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Allow reboot. If set to '0' the VM exit on reboot.\",\n        default => 1,\n    },\n    lock => {\n        optional => 1,\n        type => 'string',\n        description => \"Lock/unlock the VM.\",\n        enum => [\n            qw(backup clone create migrate rollback snapshot snapshot-delete suspending suspended)],\n    },\n    cpulimit => {\n        optional => 1,\n        type => 'number',\n        description => \"Limit of CPU usage.\",\n        verbose_description => \"Limit of CPU usage.\\n\\nNOTE: If the computer has 2 CPUs, it has\"\n            . \" total of '2' CPU time. Value '0' indicates no CPU limit.\",\n        minimum => 0,\n        maximum => 128,\n        default => 0,\n    },\n    cpuunits => {\n        optional => 1,\n        type => 'integer',\n        description => \"CPU weight for a VM, will be clamped to [1, 10000] in cgroup v2.\",\n        verbose_description =>\n            \"CPU weight for a VM. Argument is used in the kernel fair scheduler.\"\n            . \" The larger the number is, the more CPU time this VM gets. Number is relative to\"\n            . \" weights of all the other running VMs.\",\n        minimum => 1,\n        maximum => 262144,\n        default => 'cgroup v1: 1024, cgroup v2: 100',\n    },\n    memory => {\n        optional => 1,\n        type => 'string',\n        description => \"Memory properties.\",\n        format => $PVE::QemuServer::Memory::memory_fmt,\n    },\n    'amd-sev' => {\n        description => \"Secure Encrypted Virtualization (SEV) features by AMD CPUs\",\n        optional => 1,\n        format => 'pve-qemu-sev-fmt',\n        type => 'string',\n    },\n    'intel-tdx' => {\n        description => \"Trusted Domain Extension (TDX) features by Intel CPUs\",\n        optional => 1,\n        format => 'pve-qemu-tdx-fmt',\n        type => 'string',\n    },\n    balloon => {\n        optional => 1,\n        type => 'integer',\n        description =>\n            \"Amount of target RAM for the VM in MiB. The balloon driver is enabled by default,\"\n            . \" unless it is explicitly disabled by setting the value to zero.\",\n        minimum => 0,\n    },\n    shares => {\n        optional => 1,\n        type => 'integer',\n        description =>\n            \"Amount of memory shares for auto-ballooning. The larger the number is, the\"\n            . \" more memory this VM gets. Number is relative to weights of all other running VMs.\"\n            . \" Using zero disables auto-ballooning. Auto-ballooning is done by pvestatd.\",\n        minimum => 0,\n        maximum => 50000,\n        default => 1000,\n    },\n    keyboard => {\n        optional => 1,\n        type => 'string',\n        description =>\n            \"Keyboard layout for VNC server. This option is generally not required and\"\n            . \" is often better handled from within the guest OS.\",\n        enum => PVE::Tools::kvmkeymaplist(),\n        default => undef,\n    },\n    name => {\n        optional => 1,\n        type => 'string',\n        format => 'dns-name',\n        description => \"Set a name for the VM. Only used on the configuration web interface.\",\n    },\n    scsihw => {\n        optional => 1,\n        type => 'string',\n        description => \"SCSI controller model\",\n        enum => [qw(lsi lsi53c810 virtio-scsi-pci virtio-scsi-single megasas pvscsi)],\n        default => 'lsi',\n    },\n    description => {\n        optional => 1,\n        type => 'string',\n        description => \"Description for the VM. Shown in the web-interface VM's summary.\"\n            . \" This is saved as comment inside the configuration file.\",\n        maxLength => 1024 * 8,\n    },\n    ostype => {\n        optional => 1,\n        type => 'string',\n        # NOTE: When extending, also consider extending `%guest_types` in `Import/ESXi.pm`.\n        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 win10 win11 l24 l26 solaris)],\n        default => 'other',\n        description => \"Specify guest operating system.\",\n        verbose_description => <<EODESC,\nSpecify guest operating system. This is used to enable special\noptimization/features for specific operating systems:\n\n[horizontal]\nother;; unspecified OS\nwxp;; Microsoft Windows XP\nw2k;; Microsoft Windows 2000\nw2k3;; Microsoft Windows 2003\nw2k8;; Microsoft Windows 2008\nwvista;; Microsoft Windows Vista\nwin7;; Microsoft Windows 7\nwin8;; Microsoft Windows 8/2012/2012r2\nwin10;; Microsoft Windows 10/2016/2019\nwin11;; Microsoft Windows 11/2022/2025\nl24;; Linux 2.4 Kernel\nl26;; Linux 2.6 - 7.X Kernel\nsolaris;; Solaris/OpenSolaris/OpenIndiania kernel\nEODESC\n    },\n    boot => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-qm-boot',\n        description =>\n            \"Specify guest boot order. Use the 'order=' sub-property as usage with no\"\n            . \" key or 'legacy=' is deprecated.\",\n    },\n    bootdisk => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-qm-bootdisk',\n        description =>\n            \"Enable booting from specified disk. Deprecated: Use 'boot: order=foo;bar' instead.\",\n        pattern => '(ide|sata|scsi|virtio)\\d+',\n    },\n    smp => {\n        optional => 1,\n        type => 'integer',\n        description => \"The number of CPUs. Please use option -sockets instead.\",\n        minimum => 1,\n        default => 1,\n    },\n    sockets => {\n        optional => 1,\n        type => 'integer',\n        description => \"The number of CPU sockets.\",\n        minimum => 1,\n        default => 1,\n    },\n    cores => {\n        optional => 1,\n        type => 'integer',\n        description => \"The number of cores per socket.\",\n        minimum => 1,\n        default => 1,\n    },\n    numa => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Enable/disable NUMA.\",\n        default => 0,\n    },\n    hugepages => {\n        optional => 1,\n        type => 'string',\n        description =>\n            \"Enables hugepages memory.\\n\\nSets the size of hugepages in MiB. If the value \"\n            . \"is set to 'any' then 1 GiB hugepages will be used if possible, \"\n            . \"otherwise the size will fall back to 2 MiB.\",\n        enum => [qw(any 2 1024)],\n    },\n    keephugepages => {\n        optional => 1,\n        type => 'boolean',\n        default => 0,\n        description =>\n            \"Use together with hugepages. If enabled, hugepages will not not be deleted\"\n            . \" after VM shutdown and can be used for subsequent starts.\",\n    },\n    vcpus => {\n        optional => 1,\n        type => 'integer',\n        description => \"Number of hotplugged vcpus.\",\n        minimum => 1,\n        default => 0,\n    },\n    acpi => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Enable/disable ACPI.\",\n        default => 1,\n    },\n    agent => {\n        optional => 1,\n        description =>\n            \"Enable/disable communication with the QEMU Guest Agent and its properties.\",\n        type => 'string',\n        format => $PVE::QemuServer::Agent::agent_fmt,\n    },\n    kvm => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Enable/disable KVM hardware virtualization.\",\n        default => 1,\n    },\n    tdf => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Enable/disable time drift fix.\",\n        default => 0,\n    },\n    localtime => {\n        optional => 1,\n        type => 'boolean',\n        description =>\n            \"Set the real time clock (RTC) to local time. This is enabled by default if\"\n            . \" the `ostype` indicates a Microsoft Windows OS.\",\n    },\n    freeze => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Freeze CPU at startup (use 'c' monitor command to start execution).\",\n    },\n    vga => {\n        optional => 1,\n        type => 'string',\n        format => $vga_fmt,\n        description => \"Configure the VGA hardware.\",\n        verbose_description => \"Configure the VGA Hardware. If you want to use high resolution\"\n            . \" modes (>= 1280x1024x16) you may need to increase the vga memory option. Since QEMU\"\n            . \" 2.9 the default VGA display type is 'std' for all OS types besides some Windows\"\n            . \" versions (XP and older) which use 'cirrus'. The 'qxl' option enables the SPICE\"\n            . \" display server. For win* OS you can select how many independent displays you want,\"\n            . \" Linux guests can add displays them self.\\nYou can also run without any graphic card,\"\n            . \" using a serial device as terminal.\",\n    },\n    watchdog => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-qm-watchdog',\n        description => \"Create a virtual hardware watchdog device.\",\n        verbose_description =>\n            \"Create a virtual hardware watchdog device. Once enabled (by a guest\"\n            . \" action), the watchdog must be periodically polled by an agent inside the guest or\"\n            . \" else the watchdog will reset the guest (or execute the respective action specified)\",\n    },\n    startdate => {\n        optional => 1,\n        type => 'string',\n        typetext => \"(now | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS)\",\n        description => \"Set the initial date of the real time clock. Valid format for date are:\"\n            . \"'now' or '2006-06-17T16:01:21' or '2006-06-17'.\",\n        pattern => '(now|\\d{4}-\\d{1,2}-\\d{1,2}(T\\d{1,2}:\\d{1,2}:\\d{1,2})?)',\n        default => 'now',\n    },\n    startup => get_standard_option('pve-startup-order'),\n    template => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Enable/disable Template.\",\n        default => 0,\n    },\n    args => {\n        optional => 1,\n        type => 'string',\n        description => \"Arbitrary arguments passed to kvm.\",\n        verbose_description => <<EODESCR,\nArbitrary arguments passed to kvm, for example:\n\nargs: -no-reboot -smbios 'type=0,vendor=FOO'\n\nNOTE: this option is for experts only.\nEODESCR\n    },\n    tablet => {\n        optional => 1,\n        type => 'boolean',\n        default => 1,\n        description => \"Enable/disable the USB tablet device.\",\n        verbose_description =>\n            \"Enable/disable the USB tablet device. This device is usually needed\"\n            . \" to allow absolute mouse positioning with VNC. Else the mouse runs out of sync with\"\n            . \" normal VNC clients. If you're running lots of console-only guests on one host, you\"\n            . \" may consider disabling this to save some context switches. This is turned off by\"\n            . \" default if you use spice (`qm set <vmid> --vga qxl`).\",\n    },\n    migrate_speed => {\n        optional => 1,\n        type => 'integer',\n        description => \"Set maximum speed (in MB/s) for migrations. Value 0 is no limit.\",\n        minimum => 0,\n        default => 0,\n    },\n    migrate_downtime => {\n        optional => 1,\n        type => 'number',\n        description => \"Set maximum tolerated downtime (in seconds) for migrations. Should the\"\n            . \" migration not be able to converge in the very end, because too much newly dirtied\"\n            . \" RAM needs to be transferred, the limit will be increased automatically step-by-step\"\n            . \" until migration can converge. Will be capped to 2000 seconds (maximum in QEMU).\",\n        minimum => 0,\n        default => 0.1,\n    },\n    cdrom => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-qm-ide',\n        typetext => '<volume>',\n        description => \"This is an alias for option -ide2\",\n    },\n    cpu => {\n        optional => 1,\n        description => \"Emulated CPU type.\",\n        type => 'string',\n        format => 'pve-vm-cpu-conf',\n    },\n    parent => get_standard_option(\n        'pve-snapshot-name',\n        {\n            optional => 1,\n            description =>\n                \"Parent snapshot name. This is used internally, and should not be modified.\",\n        },\n    ),\n    snaptime => {\n        optional => 1,\n        description => \"Timestamp for snapshots.\",\n        type => 'integer',\n        minimum => 0,\n    },\n    vmstate => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-volume-id',\n        description =>\n            \"Reference to a volume which stores the VM state. This is used internally\"\n            . \" for snapshots.\",\n    },\n    vmstatestorage => get_standard_option(\n        'pve-storage-id',\n        {\n            description => \"Default storage for VM state volumes/files.\",\n            optional => 1,\n        },\n    ),\n    runningmachine => get_standard_option(\n        'pve-qemu-machine',\n        {\n            description =>\n                \"Specifies the QEMU machine type of the running vm. This is used internally\"\n                . \" for snapshots.\",\n        },\n    ),\n    runningcpu => {\n        description => \"Specifies the QEMU '-cpu' parameter of the running vm. This is used\"\n            . \" internally for snapshots.\",\n        optional => 1,\n        type => 'string',\n        pattern => $PVE::QemuServer::CPUConfig::qemu_cmdline_cpu_re,\n        format_description => 'QEMU -cpu parameter',\n    },\n    'running-nets-host-mtu' => {\n        type => 'string',\n        pattern => 'net\\d+=\\d+(,net\\d+=\\d+)*',\n        optional => 1,\n        description =>\n            'List of VirtIO network devices and their effective host_mtu setting. A value of 0'\n            . ' means that the host_mtu parameter is to be avoided for the corresponding device.'\n            . ' This is used internally for snapshots.',\n    },\n    machine => get_standard_option('pve-qemu-machine'),\n    arch => get_standard_option('pve-qm-cpu-arch', { optional => 1 }),\n    smbios1 => {\n        description => \"Specify SMBIOS type 1 fields.\",\n        type => 'string',\n        format => 'pve-qm-smbios1',\n        maxLength => 512,\n        optional => 1,\n    },\n    protection => {\n        optional => 1,\n        type => 'boolean',\n        description => \"Sets the protection flag of the VM. This will disable the remove VM and\"\n            . \" remove disk operations.\",\n        default => 0,\n    },\n    bios => {\n        optional => 1,\n        type => 'string',\n        enum => [qw(seabios ovmf)],\n        description => \"Select BIOS implementation.\",\n        default => 'seabios',\n    },\n    vmgenid => {\n        type => 'string',\n        pattern => '(?:[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}|[01])',\n        format_description => 'UUID',\n        description =>\n            \"Set VM Generation ID. Use '1' to autogenerate on create or update, pass '0'\"\n            . \" to disable explicitly.\",\n        verbose_description => \"The VM generation ID (vmgenid) device exposes a 128-bit integer\"\n            . \" value identifier to the guest OS. This allows to notify the guest operating system\"\n            . \" when the virtual machine is executed with a different configuration (e.g. snapshot\"\n            . \" execution or creation from a template). The guest operating system notices the\"\n            . \" change, and is then able to react as appropriate by marking its copies of\"\n            . \" distributed databases as dirty, re-initializing its random number generator, etc.\\n\"\n            . \"Note that auto-creation only works when done through API/CLI create or update methods\"\n            . \", but not when manually editing the config file.\",\n        default => \"1 (autogenerated)\",\n        optional => 1,\n    },\n    hookscript => {\n        type => 'string',\n        format => 'pve-volume-id',\n        optional => 1,\n        description => \"Script that will be executed during various steps in the vms lifetime.\",\n    },\n    ivshmem => {\n        type => 'string',\n        format => $ivshmem_fmt,\n        description =>\n            \"Inter-VM shared memory. Useful for direct communication between VMs, or to\"\n            . \" the host.\",\n        optional => 1,\n    },\n    audio0 => {\n        type => 'string',\n        format => $audio_fmt,\n        description => \"Configure a audio device, useful in combination with QXL/Spice.\",\n        optional => 1,\n    },\n    spice_enhancements => {\n        type => 'string',\n        format => $spice_enhancements_fmt,\n        description => \"Configure additional enhancements for SPICE.\",\n        optional => 1,\n    },\n    tags => {\n        type => 'string',\n        format => 'pve-tag-list',\n        description => 'Tags of the VM. This is only meta information.',\n        optional => 1,\n    },\n    rng0 => {\n        type => 'string',\n        format => 'pve-qm-rng',\n        description => \"Configure a VirtIO-based Random Number Generator.\",\n        optional => 1,\n    },\n    meta => {\n        type => 'string',\n        format => $PVE::QemuServer::MetaInfo::meta_info_fmt,\n        description => \"Some (read-only) meta-information about this guest.\",\n        optional => 1,\n    },\n    affinity => {\n        type => 'string',\n        format => 'pve-cpuset',\n        description =>\n            \"List of host cores used to execute guest processes, for example: 0,5,8-11\",\n        optional => 1,\n    },\n    'allow-ksm' => {\n        type => 'boolean',\n        description => \"Allow memory pages of this guest to be merged via KSM (Kernel Samepage\"\n            . \" Merging).\",\n        optional => 1,\n        default => 1,\n    },\n};\n\nmy $cicustom_fmt = {\n    meta => {\n        type => 'string',\n        optional => 1,\n        description => 'Specify a custom file containing all meta data passed to the VM via'\n            . ' cloud-init. This is provider specific meaning configdrive2 and nocloud differ.',\n        format => 'pve-volume-id',\n        format_description => 'volume',\n    },\n    network => {\n        type => 'string',\n        optional => 1,\n        description =>\n            'To pass a custom file containing all network data to the VM via cloud-init.',\n        format => 'pve-volume-id',\n        format_description => 'volume',\n    },\n    user => {\n        type => 'string',\n        optional => 1,\n        description =>\n            'To pass a custom file containing all user data to the VM via cloud-init.',\n        format => 'pve-volume-id',\n        format_description => 'volume',\n    },\n    vendor => {\n        type => 'string',\n        optional => 1,\n        description =>\n            'To pass a custom file containing all vendor data to the VM via cloud-init.',\n        format => 'pve-volume-id',\n        format_description => 'volume',\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-cicustom', $cicustom_fmt);\n\n# any new option might need to be added to $cloudinitoptions in PVE::API2::Qemu\nmy $confdesc_cloudinit = {\n    citype => {\n        optional => 1,\n        type => 'string',\n        description =>\n            'Specifies the cloud-init configuration format. The default depends on the'\n            . ' configured operating system type (`ostype`. We use the `nocloud` format for Linux,'\n            . ' and `configdrive2` for windows.',\n        enum => ['configdrive2', 'nocloud', 'opennebula'],\n    },\n    ciuser => {\n        optional => 1,\n        type => 'string',\n        description =>\n            \"cloud-init: User name to change ssh keys and password for instead of the\"\n            . \" image's configured default user.\",\n    },\n    cipassword => {\n        optional => 1,\n        type => 'string',\n        description => 'cloud-init: Password to assign the user. Using this is generally not'\n            . ' recommended. Use ssh keys instead. Also note that older cloud-init versions do not'\n            . ' support hashed passwords.',\n    },\n    ciupgrade => {\n        optional => 1,\n        type => 'boolean',\n        description => 'cloud-init: do an automatic package upgrade after the first boot.',\n        default => 1,\n    },\n    cicustom => {\n        optional => 1,\n        type => 'string',\n        description => 'cloud-init: Specify custom files to replace the automatically generated'\n            . ' ones at start.',\n        format => 'pve-qm-cicustom',\n    },\n    searchdomain => {\n        optional => 1,\n        type => 'string',\n        description => 'cloud-init: Sets DNS search domains for a container. Create will'\n            . ' automatically use the setting from the host if neither searchdomain nor nameserver'\n            . ' are set.',\n    },\n    nameserver => {\n        optional => 1,\n        type => 'string',\n        format => 'address-list',\n        description => 'cloud-init: Sets DNS server IP address for a container. Create will'\n            . ' automatically use the setting from the host if neither searchdomain nor nameserver'\n            . ' are set.',\n    },\n    sshkeys => {\n        optional => 1,\n        type => 'string',\n        format => 'urlencoded',\n        description => \"cloud-init: Setup public SSH keys (one key per line, OpenSSH format).\",\n    },\n};\n\n# what about other qemu settings ?\n#cpu => 'string',\n#machine => 'string',\n#fda => 'file',\n#fdb => 'file',\n#mtdblock => 'file',\n#sd => 'file',\n#pflash => 'file',\n#snapshot => 'bool',\n#bootp => 'file',\n##tftp => 'dir',\n##smb => 'dir',\n#kernel => 'file',\n#append => 'string',\n#initrd => 'file',\n##soundhw => 'string',\n\nwhile (my ($k, $v) = each %$confdesc) {\n    PVE::JSONSchema::register_standard_option(\"pve-qm-$k\", $v);\n}\n\nmy $MAX_NETS = 32;\nmy $MAX_SERIAL_PORTS = 4;\nmy $MAX_PARALLEL_PORTS = 3;\n\nfor (my $i = 0; $i < $PVE::QemuServer::Memory::MAX_NUMA; $i++) {\n    $confdesc->{\"numa$i\"} = $PVE::QemuServer::Memory::numadesc;\n}\n\nfor (my $i = 0; $i < max_virtiofs(); $i++) {\n    $confdesc->{\"virtiofs$i\"} = get_standard_option('pve-qm-virtiofs');\n}\n\nfor (my $i = 0; $i < $MAX_NETS; $i++) {\n    $confdesc->{\"net$i\"} = $PVE::QemuServer::Network::netdesc;\n    $confdesc_cloudinit->{\"ipconfig$i\"} = $PVE::QemuServer::Network::ipconfigdesc;\n}\n\nforeach my $key (keys %$confdesc_cloudinit) {\n    $confdesc->{$key} = $confdesc_cloudinit->{$key};\n}\n\nPVE::JSONSchema::register_format('pve-cpuset', \\&pve_verify_cpuset);\n\nsub pve_verify_cpuset {\n    my ($set_text, $noerr) = @_;\n\n    my ($count, $members) = eval { PVE::CpuSet::parse_cpuset($set_text) };\n\n    if ($@) {\n        return if $noerr;\n        die \"unable to parse cpuset option\\n\";\n    }\n\n    return PVE::CpuSet->new($members)->short_string();\n}\n\nPVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \\&verify_volume_id_or_qm_path);\n\nsub verify_volume_id_or_qm_path {\n    my ($volid, $noerr) = @_;\n\n    return $volid if $volid eq 'none' || $volid eq 'cdrom';\n\n    return verify_volume_id_or_absolute_path($volid, $noerr);\n}\n\nPVE::JSONSchema::register_format(\n    'pve-volume-id-or-absolute-path',\n    \\&verify_volume_id_or_absolute_path,\n);\n\nsub verify_volume_id_or_absolute_path {\n    my ($volid, $noerr) = @_;\n\n    return $volid if $volid =~ m|^/|;\n\n    $volid = eval { PVE::JSONSchema::check_format('pve-volume-id', $volid, '') };\n    if ($@) {\n        return if $noerr;\n        die $@;\n    }\n    return $volid;\n}\n\nmy $serialdesc = {\n    optional => 1,\n    type => 'string',\n    pattern => '(/dev/.+|socket)',\n    description => \"Create a serial device inside the VM (n is 0 to 3)\",\n    verbose_description => <<EODESCR,\nCreate a serial device inside the VM (n is 0 to 3), and pass through a\nhost serial device (i.e. /dev/ttyS0), or create a unix socket on the\nhost side (use 'qm terminal' to open a terminal connection).\n\nNOTE: If you pass through a host serial device, it is no longer possible to migrate such machines -\nuse with special care.\n\nCAUTION: Experimental! User reported problems with this option.\nEODESCR\n};\n\nmy $paralleldesc = {\n    optional => 1,\n    type => 'string',\n    pattern => '/dev/parport\\d+|/dev/usb/lp\\d+',\n    description => \"Map host parallel devices (n is 0 to 2).\",\n    verbose_description => <<EODESCR,\nMap host parallel devices (n is 0 to 2).\n\nNOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such\nmachines - use with special care.\n\nCAUTION: Experimental! User reported problems with this option.\nEODESCR\n};\n\nfor (my $i = 0; $i < $MAX_PARALLEL_PORTS; $i++) {\n    $confdesc->{\"parallel$i\"} = $paralleldesc;\n}\n\nfor (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {\n    $confdesc->{\"serial$i\"} = $serialdesc;\n}\n\nfor (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) {\n    $confdesc->{\"hostpci$i\"} = $PVE::QemuServer::PCI::hostpcidesc;\n}\n\nfor my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) {\n    $confdesc->{$key} = $PVE::QemuServer::Drive::drivedesc_hash->{$key};\n}\n\nfor (my $i = 0; $i < $PVE::QemuServer::USB::MAX_USB_DEVICES; $i++) {\n    $confdesc->{\"usb$i\"} = $PVE::QemuServer::USB::usbdesc;\n}\n\nmy $boot_fmt = {\n    legacy => {\n        optional => 1,\n        default_key => 1,\n        type => 'string',\n        description => \"Boot on floppy (a), hard disk (c), CD-ROM (d), or network (n).\"\n            . \" Deprecated, use 'order=' instead.\",\n        pattern => '[acdn]{1,4}',\n        format_description => \"[acdn]{1,4}\",\n\n        # note: this is also the fallback if boot: is not given at all\n        default => 'cdn',\n    },\n    order => {\n        optional => 1,\n        type => 'string',\n        format => 'pve-qm-bootdev-list',\n        format_description => \"device[;device...]\",\n        description => <<EODESC,\nThe guest will attempt to boot from devices in the order they appear here.\n\nDisks, optical drives and passed-through storage USB devices will be directly\nbooted from, NICs will load PXE, and PCIe devices will either behave like disks\n(e.g. NVMe) or load an option ROM (e.g. RAID controller, hardware NIC).\n\nNote that only devices in this list will be marked as bootable and thus loaded\nby the guest firmware (BIOS/UEFI). If you require multiple disks for booting\n(e.g. software-raid), you need to specify all of them here.\n\nOverrides the deprecated 'legacy=[acdn]*' value when given.\nEODESC\n    },\n};\nPVE::JSONSchema::register_format('pve-qm-boot', $boot_fmt);\n\nPVE::JSONSchema::register_format('pve-qm-bootdev', \\&verify_bootdev);\n\nsub verify_bootdev {\n    my ($dev, $noerr) = @_;\n\n    my $special = $dev =~ m/^efidisk/ || $dev =~ m/^tpmstate/;\n    return $dev if PVE::QemuServer::Drive::is_valid_drivename($dev) && !$special;\n\n    my $check = sub {\n        my ($base) = @_;\n        return 0 if $dev !~ m/^$base\\d+$/;\n        return 0 if !$confdesc->{$dev};\n        return 1;\n    };\n\n    return $dev if $check->(\"net\");\n    return $dev if $check->(\"usb\");\n    return $dev if $check->(\"hostpci\");\n\n    return if $noerr;\n    die \"invalid boot device '$dev'\\n\";\n}\n\nsub print_bootorder {\n    my ($devs) = @_;\n    return \"\" if !@$devs;\n    my $data = { order => join(';', @$devs) };\n    return PVE::JSONSchema::print_property_string($data, $boot_fmt);\n}\n\nmy $kvm_api_version = 0;\n\nsub kvm_version {\n    return $kvm_api_version if $kvm_api_version;\n\n    open my $fh, '<', '/dev/kvm' or return;\n\n    # 0xae00 => KVM_GET_API_VERSION\n    $kvm_api_version = ioctl($fh, 0xae00, 0);\n    close($fh);\n\n    return $kvm_api_version;\n}\n\nmy sub extract_version {\n    my ($machine_type, $version) = @_;\n    $version = kvm_user_version() if !defined($version);\n    return PVE::QemuServer::Machine::extract_version($machine_type, $version);\n}\n\nsub kernel_has_vhost_net {\n    return -c '/dev/vhost-net';\n}\n\nsub option_exists {\n    my $key = shift;\n    return defined($confdesc->{$key});\n}\n\n# try to convert old style file names to volume IDs\nsub filename_to_volume_id {\n    my ($vmid, $file, $media) = @_;\n\n    if (!(\n        $file eq 'none'\n        || $file eq 'cdrom'\n        || $file =~ m|^/dev/.+|\n        || $file =~ m/^([^:]+):(.+)$/\n    )) {\n\n        return if $file =~ m|/|;\n\n        if ($media && $media eq 'cdrom') {\n            $file = \"local:iso/$file\";\n        } else {\n            $file = \"local:$vmid/$file\";\n        }\n    }\n\n    return $file;\n}\n\nsub verify_media_type {\n    my ($opt, $vtype, $media) = @_;\n\n    return if !$media;\n\n    my $etype;\n    if ($media eq 'disk') {\n        $etype = 'images';\n    } elsif ($media eq 'cdrom') {\n        $etype = 'iso';\n    } else {\n        die \"internal error\";\n    }\n\n    return if ($vtype eq $etype);\n\n    raise_param_exc({ $opt => \"unexpected media type ($vtype != $etype)\" });\n}\n\nsub cleanup_drive_path {\n    my ($opt, $storecfg, $drive) = @_;\n\n    # try to convert filesystem paths to volume IDs\n\n    if (\n        ($drive->{file} !~ m/^(cdrom|none)$/)\n        && ($drive->{file} !~ m|^/dev/.+|)\n        && ($drive->{file} !~ m/^([^:]+):(.+)$/)\n        && ($drive->{file} !~ m/^\\d+$/)\n    ) {\n        my ($vtype, $volid) = PVE::Storage::path_to_volume_id($storecfg, $drive->{file});\n        raise_param_exc({ $opt => \"unable to associate path '$drive->{file}' to any storage\" })\n            if !$vtype;\n        $drive->{media} = 'cdrom' if !$drive->{media} && $vtype eq 'iso';\n        verify_media_type($opt, $vtype, $drive->{media});\n        $drive->{file} = $volid;\n    }\n\n    $drive->{media} = 'cdrom' if !$drive->{media} && $drive->{file} =~ m/^(cdrom|none)$/;\n}\n\nsub parse_hotplug_features {\n    my ($data) = @_;\n\n    my $res = {};\n\n    return $res if $data eq '0';\n\n    $data = $confdesc->{hotplug}->{default} if $data eq '1';\n\n    foreach my $feature (PVE::Tools::split_list($data)) {\n        if ($feature =~ m/^(network|disk|cpu|memory|usb|cloudinit)$/) {\n            $res->{$1} = 1;\n        } else {\n            die \"invalid hotplug feature '$feature'\\n\";\n        }\n    }\n    return $res;\n}\n\nPVE::JSONSchema::register_format('pve-hotplug-features', \\&pve_verify_hotplug_features);\n\nsub pve_verify_hotplug_features {\n    my ($value, $noerr) = @_;\n\n    return $value if parse_hotplug_features($value);\n\n    return if $noerr;\n\n    die \"unable to parse hotplug option\\n\";\n}\n\nsub assert_clipboard_config {\n    my ($vga) = @_;\n\n    my $clipboard_regex = qr/^(std|cirrus|vmware|virtio|qxl)/;\n\n    if (\n        $vga->{'clipboard'}\n        && $vga->{'clipboard'} eq 'vnc'\n        && $vga->{type}\n        && $vga->{type} !~ $clipboard_regex\n    ) {\n        die \"vga type $vga->{type} is not compatible with VNC clipboard\\n\";\n    }\n}\n\nsub print_tabletdevice_full {\n    my ($conf, $arch) = @_;\n\n    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n\n    # we use uhci for old VMs because tablet driver was buggy in older qemu\n    my $usbbus;\n    if ($q35 || $arch eq 'aarch64') {\n        $usbbus = 'ehci';\n    } else {\n        $usbbus = 'uhci';\n    }\n\n    return \"usb-tablet,id=tablet,bus=$usbbus.0,port=1\";\n}\n\nsub print_keyboarddevice_full {\n    my ($conf, $arch) = @_;\n\n    return if $arch ne 'aarch64';\n\n    return \"usb-kbd,id=keyboard,bus=ehci.0,port=2\";\n}\n\nsub print_drive_commandline_full {\n    my ($storecfg, $vmid, $drive, $live_restore_name) = @_;\n\n    my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);\n\n    my ($storeid) = PVE::Storage::parse_volume_id($drive->{file}, 1);\n    my $scfg = $storeid ? PVE::Storage::storage_config($storecfg, $storeid) : undef;\n    my $vtype = $storeid ? (PVE::Storage::parse_volname($storecfg, $drive->{file}))[0] : undef;\n\n    my ($path, $format) =\n        PVE::QemuServer::Drive::get_path_and_format($storecfg, $drive, $live_restore_name);\n\n    if ($scfg && $scfg->{'snapshot-as-volume-chain'} && $format && $format eq 'qcow2') {\n        # the print_drive_commandline_full() function is only used if machine version is < 10.0\n        die \"storage for '$drive->{file}' is configured for snapshots as a volume chain - this\"\n            . \" requires QEMU machine version >= 10.0. See\"\n            . \" https://pve.proxmox.com/wiki/QEMU_Machine_Version_Upgrade\\n\";\n    }\n\n    my $is_rbd = $path =~ m/^rbd:/;\n\n    my $opts = '';\n    my @qemu_drive_options = qw(media cache rerror werror discard);\n    foreach my $o (@qemu_drive_options) {\n        $opts .= \",$o=$drive->{$o}\" if defined($drive->{$o});\n    }\n\n    # snapshot only accepts on|off\n    if (defined($drive->{snapshot})) {\n        my $v = $drive->{snapshot} ? 'on' : 'off';\n        $opts .= \",snapshot=$v\";\n    }\n\n    if (defined($drive->{ro})) { # ro maps to QEMUs `readonly`, which accepts `on` or `off` only\n        $opts .= \",readonly=\" . ($drive->{ro} ? 'on' : 'off');\n    }\n\n    foreach my $type (['', '-total'], [_rd => '-read'], [_wr => '-write']) {\n        my ($dir, $qmpname) = @$type;\n        if (my $v = $drive->{\"mbps$dir\"}) {\n            $opts .= \",throttling.bps$qmpname=\" . int($v * 1024 * 1024);\n        }\n        if (my $v = $drive->{\"mbps${dir}_max\"}) {\n            $opts .= \",throttling.bps$qmpname-max=\" . int($v * 1024 * 1024);\n        }\n        if (my $v = $drive->{\"bps${dir}_max_length\"}) {\n            $opts .= \",throttling.bps$qmpname-max-length=$v\";\n        }\n        if (my $v = $drive->{\"iops${dir}\"}) {\n            $opts .= \",throttling.iops$qmpname=$v\";\n        }\n        if (my $v = $drive->{\"iops${dir}_max\"}) {\n            $opts .= \",throttling.iops$qmpname-max=$v\";\n        }\n        if (my $v = $drive->{\"iops${dir}_max_length\"}) {\n            $opts .= \",throttling.iops$qmpname-max-length=$v\";\n        }\n    }\n\n    if ($live_restore_name) {\n        $format = \"rbd\" if $is_rbd;\n        die \"$drive_id: Proxmox Backup Server backed drive cannot auto-detect the format\\n\"\n            if !$format;\n        $opts .= \",format=alloc-track,file.driver=$format\";\n    } elsif ($format) {\n        $opts .= \",format=$format\";\n    }\n\n    my $cache_direct = PVE::QemuServer::Drive::drive_uses_cache_direct($drive, $scfg);\n\n    $opts .= \",cache=none\" if !$drive->{cache} && $cache_direct;\n\n    my $aio = PVE::QemuServer::Drive::aio_cmdline_option($scfg, $drive, $cache_direct);\n    $opts .= \",aio=$aio\";\n\n    die \"$drive_id: explicit media parameter is required for iso images\\n\"\n        if !defined($drive->{media}) && defined($vtype) && $vtype eq 'iso';\n\n    if (!drive_is_cdrom($drive)) {\n        my $detectzeroes = PVE::QemuServer::Drive::detect_zeroes_cmdline_option($drive);\n\n        # note: 'detect-zeroes' works per blockdev and we want it to persist\n        # after the alloc-track is removed, so put it on 'file' directly\n        my $dz_param = $live_restore_name ? \"file.detect-zeroes\" : \"detect-zeroes\";\n        $opts .= \",$dz_param=$detectzeroes\" if $detectzeroes;\n    }\n\n    if ($live_restore_name) {\n        $opts .= \",backing=$live_restore_name\";\n        $opts .= \",auto-remove=on\";\n    }\n\n    # my $file_param = $live_restore_name ? \"file.file.filename\" : \"file\";\n    my $file_param = \"file\";\n    if ($live_restore_name) {\n        # non-rbd drivers require the underlying file to be a separate block\n        # node, so add a second .file indirection\n        $file_param .= \".file\" if !$is_rbd;\n        $file_param .= \".filename\";\n    }\n    my $pathinfo = $path ? \"$file_param=$path,\" : '';\n\n    return \"${pathinfo}if=none,id=drive-$drive->{interface}$drive->{index}$opts\";\n}\n\nsub print_pbs_blockdev {\n    my ($pbs_conf, $pbs_name) = @_;\n    my $blockdev = \"driver=pbs,node-name=$pbs_name,read-only=on\";\n    $blockdev .= \",repository=$pbs_conf->{repository}\";\n    $blockdev .= \",namespace=$pbs_conf->{namespace}\" if $pbs_conf->{namespace};\n    $blockdev .= \",snapshot=$pbs_conf->{snapshot}\";\n    $blockdev .= \",archive=$pbs_conf->{archive}\";\n    $blockdev .= \",keyfile=$pbs_conf->{keyfile}\" if $pbs_conf->{keyfile};\n    return $blockdev;\n}\n\nsub print_netdevice_full {\n    my (\n        $vmid,\n        $conf,\n        $net,\n        $netid,\n        $bridges,\n        $use_old_bios_files,\n        $arch,\n        $machine_version,\n        $host_mtu_migration, # force this value for host_mtu, 0 means force absence of param\n    ) = @_;\n\n    my $device = $net->{model};\n    if ($net->{model} eq 'virtio') {\n        $device = 'virtio-net-pci';\n    }\n\n    my $pciaddr = print_pci_addr(\"$netid\", $bridges, $arch);\n    my $tmpstr = \"$device,mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid\";\n    if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio') {\n        # Consider we have N queues, the number of vectors needed is 2 * N + 2, i.e., one per in\n        # and out of each queue plus one config interrupt and control vector queue\n        my $vectors = $net->{queues} * 2 + 2;\n        $tmpstr .= \",vectors=$vectors,mq=on\";\n        if (min_version($machine_version, 7, 1)) {\n            $tmpstr .= \",packed=on\";\n        }\n    }\n\n    if (min_version($machine_version, 7, 1) && $net->{model} eq 'virtio') {\n        $tmpstr .= \",rx_queue_size=1024,tx_queue_size=256\";\n    }\n\n    $tmpstr .= \",bootindex=$net->{bootindex}\" if $net->{bootindex};\n\n    my $mtu = $net->{mtu};\n\n    my $migration_skip_host_mtu = defined($host_mtu_migration) && $host_mtu_migration == 0;\n    print \"netdev $netid: not adding 'host_mtu' parameter for migration compat\\n\"\n        if $migration_skip_host_mtu;\n\n    if ($net->{model} eq 'virtio' && $net->{bridge} && !$migration_skip_host_mtu) {\n        my $bridge_mtu = PVE::Network::read_bridge_mtu($net->{bridge});\n\n        if ($host_mtu_migration) {\n            print \"netdev $netid: using 'host_mtu=$host_mtu_migration' for migration compat\\n\";\n            $mtu = $host_mtu_migration;\n        }\n\n        if (!defined($mtu) || $mtu == 1) {\n            $mtu = $bridge_mtu;\n        } elsif ($mtu < 576) {\n            die \"netdev $netid: MTU '$mtu' is smaller than the IP minimum MTU '576'\\n\";\n        } elsif ($mtu > $bridge_mtu) {\n            die \"netdev $netid: MTU '$mtu' is bigger than the bridge MTU '$bridge_mtu'\"\n                . \" - adjust the MTU for the network device in the VM configuration, while ensuring\"\n                . \" that the bridge is configured as desired.\\n\";\n        }\n\n        if (min_version($machine_version, 10, 0, 1) || $host_mtu_migration) {\n            # Always add host_mtu for migration compatibility, because the presence of host_mtu\n            # means that the virtual hardware is generated differently (at least for i440fx)\n            $tmpstr .= \",host_mtu=$mtu\";\n        } else {\n            $tmpstr .= \",host_mtu=$mtu\" if $mtu != 1500;\n        }\n    } elsif (defined($mtu)) {\n        my $msg_prefix = \"netdev $netid: ignoring MTU '$mtu'\";\n        if ($migration_skip_host_mtu) {\n            # When the machine version is less than 10.0+pve1 and the MTU is 1500, not having the\n            # host_mtu parameter is fully expected. Only log when not expected to avoid confusion.\n            if (min_version($machine_version, 10, 0, 1) || $mtu != 1500) {\n                log_warn(\n                    \"$msg_prefix, not used on the source side according to migration parameters\");\n            }\n        } elsif (!$net->{bridge}) {\n            log_warn(\"$msg_prefix, no bridge configured\");\n        } else {\n            log_warn(\"$msg_prefix, not using VirtIO\");\n        }\n    }\n\n    if ($use_old_bios_files) {\n        my $romfile;\n        if ($device eq 'virtio-net-pci') {\n            $romfile = 'pxe-virtio.rom';\n        } elsif ($device eq 'e1000') {\n            $romfile = 'pxe-e1000.rom';\n        } elsif ($device eq 'e1000e') {\n            $romfile = 'pxe-e1000e.rom';\n        } elsif ($device eq 'ne2k') {\n            $romfile = 'pxe-ne2k_pci.rom';\n        } elsif ($device eq 'pcnet') {\n            $romfile = 'pxe-pcnet.rom';\n        } elsif ($device eq 'rtl8139') {\n            $romfile = 'pxe-rtl8139.rom';\n        }\n        $tmpstr .= \",romfile=$romfile\" if $romfile;\n    }\n\n    return $tmpstr;\n}\n\nsub print_netdev_full {\n    my ($vmid, $conf, $arch, $net, $netid, $hotplug) = @_;\n\n    my $i = '';\n    if ($netid =~ m/^net(\\d+)$/) {\n        $i = int($1);\n    }\n\n    die \"got strange net id '$i'\\n\" if $i >= ${MAX_NETS};\n\n    my $ifname = \"tap${vmid}i$i\";\n\n    # kvm uses TUNSETIFF ioctl, and that limits ifname length\n    die \"interface name '$ifname' is too long (max 15 character)\\n\"\n        if length($ifname) >= 16;\n\n    my $vhostparam = '';\n    if (is_native_arch($arch)) {\n        $vhostparam = ',vhost=on' if kernel_has_vhost_net() && $net->{model} eq 'virtio';\n    }\n\n    my $vmname = $conf->{name} || \"vm$vmid\";\n\n    my $netdev = \"\";\n    my $script = $hotplug ? \"pve-bridge-hotplug\" : \"pve-bridge\";\n\n    if ($net->{bridge}) {\n        $netdev = \"type=tap,id=$netid,ifname=${ifname},script=/usr/libexec/qemu-server/$script\"\n            . \",downscript=/usr/libexec/qemu-server/pve-bridgedown$vhostparam\";\n    } else {\n        $netdev = \"type=user,id=$netid,hostname=$vmname\";\n    }\n\n    $netdev .= \",queues=$net->{queues}\" if ($net->{queues} && $net->{model} eq 'virtio');\n\n    return $netdev;\n}\n\nmy $vga_map = {\n    'cirrus' => 'cirrus-vga',\n    'std' => 'VGA',\n    'vmware' => 'vmware-svga',\n    'virtio' => 'virtio-vga',\n    'virtio-gl' => 'virtio-vga-gl',\n};\n\nsub print_vga_device {\n    my ($conf, $vga, $arch, $machine_version, $id, $qxlnum, $bridges) = @_;\n\n    my $type = $vga_map->{ $vga->{type} };\n    if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {\n        $type = 'virtio-gpu';\n    }\n    my $vgamem_mb = $vga->{memory};\n\n    my $max_outputs = '';\n    if ($qxlnum) {\n        $type = $id ? 'qxl' : 'qxl-vga';\n\n        if (!$conf->{ostype} || $conf->{ostype} =~ m/^(?:l\\d\\d)|(?:other)$/) {\n            # set max outputs so linux can have up to 4 qxl displays with one device\n            if (min_version($machine_version, 4, 1)) {\n                $max_outputs = \",max_outputs=4\";\n            }\n        }\n    }\n\n    die \"no device-type for $vga->{type}\\n\" if !$type;\n\n    my $memory = \"\";\n    if ($vgamem_mb) {\n        if ($vga->{type} =~ /^virtio/) {\n            my $bytes = PVE::Tools::convert_size($vgamem_mb, \"mb\" => \"b\");\n            $memory = \",max_hostmem=$bytes\";\n        } elsif ($qxlnum) {\n            # from https://www.spice-space.org/multiple-monitors.html\n            $memory = \",vgamem_mb=$vga->{memory}\";\n            my $ram = $vgamem_mb * 4;\n            my $vram = $vgamem_mb * 2;\n            $memory .= \",ram_size_mb=$ram,vram_size_mb=$vram\";\n        } else {\n            $memory = \",vgamem_mb=$vga->{memory}\";\n        }\n    } elsif ($qxlnum && $id) {\n        $memory = \",ram_size=67108864,vram_size=33554432\";\n    }\n\n    my $edidoff = \"\";\n    if ($type eq 'VGA' && windows_version($conf->{ostype})) {\n        $edidoff = \",edid=off\" if (!defined($conf->{bios}) || $conf->{bios} ne 'ovmf');\n    }\n\n    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n    my $vgaid = \"vga\" . ($id // '');\n    my $pciaddr;\n    if ($q35 && $vgaid eq 'vga') {\n        # the first display uses pcie.0 bus on q35 machines\n        $pciaddr = print_pcie_addr($vgaid);\n    } else {\n        $pciaddr = print_pci_addr($vgaid, $bridges, $arch);\n    }\n\n    if ($vga->{type} eq 'virtio-gl') {\n        my $base = '/usr/lib/x86_64-linux-gnu/lib';\n        die \"missing libraries for '$vga->{type}' detected! Please install 'libgl1' and 'libegl1'\\n\"\n            if !-e \"${base}EGL.so.1\" || !-e \"${base}GL.so.1\";\n\n        die\n            \"no DRM render node detected (/dev/dri/renderD*), no GPU? - needed for '$vga->{type}' display\\n\"\n            if !PVE::Tools::dir_glob_regex('/dev/dri/', \"renderD.*\");\n    }\n\n    return \"$type,id=${vgaid}${memory}${max_outputs}${pciaddr}${edidoff}\";\n}\n\nsub vm_is_volid_owner {\n    my ($storecfg, $vmid, $volid) = @_;\n\n    if ($volid !~ m|^/|) {\n        my ($path, $owner);\n        eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };\n        if ($owner && ($owner == $vmid)) {\n            return 1;\n        }\n    }\n\n    return;\n}\n\nsub vmconfig_register_unused_drive {\n    my ($storecfg, $vmid, $conf, $drive) = @_;\n\n    if (drive_is_cloudinit($drive)) {\n        eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) };\n        warn $@ if $@;\n        delete $conf->{'special-sections'}->{cloudinit};\n    } elsif (!drive_is_cdrom($drive)) {\n        my $volid = $drive->{file};\n        if (vm_is_volid_owner($storecfg, $vmid, $volid)) {\n            PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);\n        }\n    }\n}\n\n# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str][,base64=bool]\nmy $smbios1_fmt = {\n    uuid => {\n        type => 'string',\n        pattern => '[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}',\n        format_description => 'UUID',\n        description => \"Set SMBIOS1 UUID.\",\n        optional => 1,\n    },\n    version => {\n        type => 'string',\n        pattern => '[A-Za-z0-9+\\/]+={0,2}',\n        format_description => 'Base64 encoded string',\n        description => \"Set SMBIOS1 version.\",\n        optional => 1,\n    },\n    serial => {\n        type => 'string',\n        pattern => '[A-Za-z0-9+\\/]+={0,2}',\n        format_description => 'Base64 encoded string',\n        description => \"Set SMBIOS1 serial number.\",\n        optional => 1,\n    },\n    manufacturer => {\n        type => 'string',\n        pattern => '[A-Za-z0-9+\\/]+={0,2}',\n        format_description => 'Base64 encoded string',\n        description => \"Set SMBIOS1 manufacturer.\",\n        optional => 1,\n    },\n    product => {\n        type => 'string',\n        pattern => '[A-Za-z0-9+\\/]+={0,2}',\n        format_description => 'Base64 encoded string',\n        description => \"Set SMBIOS1 product ID.\",\n        optional => 1,\n    },\n    sku => {\n        type => 'string',\n        pattern => '[A-Za-z0-9+\\/]+={0,2}',\n        format_description => 'Base64 encoded string',\n        description => \"Set SMBIOS1 SKU string.\",\n        optional => 1,\n    },\n    family => {\n        type => 'string',\n        pattern => '[A-Za-z0-9+\\/]+={0,2}',\n        format_description => 'Base64 encoded string',\n        description => \"Set SMBIOS1 family string.\",\n        optional => 1,\n    },\n    base64 => {\n        type => 'boolean',\n        description => 'Flag to indicate that the SMBIOS values are base64 encoded',\n        optional => 1,\n    },\n};\n\nsub parse_smbios1 {\n    my ($data) = @_;\n\n    my $res = eval { parse_property_string($smbios1_fmt, $data) };\n    warn $@ if $@;\n    return $res;\n}\n\nsub print_smbios1 {\n    my ($smbios1) = @_;\n    return PVE::JSONSchema::print_property_string($smbios1, $smbios1_fmt);\n}\n\nPVE::JSONSchema::register_format('pve-qm-smbios1', $smbios1_fmt);\n\nsub parse_watchdog {\n    my ($value) = @_;\n\n    return if !$value;\n\n    my $res = eval { parse_property_string($watchdog_fmt, $value) };\n    warn $@ if $@;\n    return $res;\n}\n\nsub parse_vga {\n    my ($value) = @_;\n\n    return {} if !$value;\n    my $res = eval { parse_property_string($vga_fmt, $value) };\n    warn $@ if $@;\n    return $res;\n}\n\nsub qemu_created_version_fixups {\n    my ($conf, $forcemachine, $kvmver) = @_;\n\n    my $meta = PVE::QemuServer::MetaInfo::parse_meta_info($conf->{meta}) // {};\n    my $forced_vers = PVE::QemuServer::Machine::extract_version($forcemachine);\n\n    # check if we need to apply some handling for VMs that always use the latest machine version but\n    # had a machine version transition happen that affected HW such that, e.g., an OS config change\n    # would be required (we do not want to pin machine version for non-windows OS type)\n    my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine});\n    if (\n        (!defined($machine_conf->{type}) || $machine_conf->{type} =~ m/^(?:pc|q35|virt)$/) # non-versioned machine\n        && (!defined($meta->{'creation-qemu'})\n            || !min_version($meta->{'creation-qemu'}, 6, 1)) # created before 6.1\n        && (!$forced_vers || min_version($forced_vers, 6, 1)) # handle snapshot-rollback/migrations\n        && min_version($kvmver, 6, 1) # only need to apply the change since 6.1\n    ) {\n        my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n        if ($q35 && $conf->{ostype} && $conf->{ostype} eq 'l26') {\n            # this changed to default-on in Q 6.1 for q35 machines, it will mess with PCI slot view\n            # and thus with the predictable interface naming of systemd\n            return ['-global', 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off'];\n        }\n    }\n    return;\n}\n\n# add JSON properties for create and set function\nsub json_config_properties {\n    my ($prop, $with_disk_alloc, $with_snapshot_info) = @_;\n\n    my $skip_json_config_opts;\n    if (!$with_snapshot_info) {\n        $skip_json_config_opts = {\n            parent => 1,\n            snaptime => 1,\n            vmstate => 1,\n            'running-nets-host-mtu' => 1,\n            runningmachine => 1,\n            runningcpu => 1,\n            meta => 1,\n        };\n    }\n\n    foreach my $opt (keys %$confdesc) {\n        next if $skip_json_config_opts->{$opt};\n\n        if ($with_disk_alloc && is_valid_drivename($opt)) {\n            $prop->{$opt} = $PVE::QemuServer::Drive::drivedesc_hash_with_alloc->{$opt};\n        } else {\n            $prop->{$opt} = $confdesc->{$opt};\n        }\n    }\n\n    return $prop;\n}\n\n# Properties that we can read from an OVF file\nsub json_ovf_properties {\n    my $prop = {};\n\n    for my $device (PVE::QemuServer::Drive::valid_drive_names()) {\n        $prop->{$device} = {\n            type => 'string',\n            format => 'pve-volume-id-or-absolute-path',\n            description => \"Disk image that gets imported to $device\",\n            optional => 1,\n        };\n    }\n\n    $prop->{cores} = {\n        type => 'integer',\n        description => \"The number of CPU cores.\",\n        optional => 1,\n    };\n    $prop->{memory} = {\n        type => 'integer',\n        description => \"Amount of RAM for the VM in MB.\",\n        optional => 1,\n    };\n    $prop->{name} = {\n        type => 'string',\n        description => \"Name of the VM.\",\n        optional => 1,\n    };\n\n    return $prop;\n}\n\n# return copy of $confdesc_cloudinit to generate documentation\nsub cloudinit_config_properties {\n\n    return dclone($confdesc_cloudinit);\n}\n\nsub cloudinit_pending_properties {\n    my $p = {\n        map { $_ => 1 } keys $confdesc_cloudinit->%*, name => 1,\n    };\n    $p->{\"net$_\"} = 1 for 0 .. ($MAX_NETS - 1);\n    return $p;\n}\n\nsub check_type {\n    my ($key, $value, $schema) = @_;\n\n    die \"check_type: no schema defined\\n\" if !$schema;\n\n    die \"unknown setting '$key'\\n\" if !$schema->{$key};\n\n    my $type = $schema->{$key}->{type};\n\n    if (!defined($value)) {\n        die \"got undefined value\\n\";\n    }\n\n    if ($value =~ m/[\\n\\r]/) {\n        die \"property contains a line feed\\n\";\n    }\n\n    if ($type eq 'boolean') {\n        return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);\n        return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);\n        die \"type check ('boolean') failed - got '$value'\\n\";\n    } elsif ($type eq 'integer') {\n        return int($1) if $value =~ m/^(\\d+)$/;\n        die \"type check ('integer') failed - got '$value'\\n\";\n    } elsif ($type eq 'number') {\n        return $value if $value =~ m/^(\\d+)(\\.\\d+)?$/;\n        die \"type check ('number') failed - got '$value'\\n\";\n    } elsif ($type eq 'string') {\n        if (my $fmt = $schema->{$key}->{format}) {\n            PVE::JSONSchema::check_format($fmt, $value);\n            return $value;\n        }\n        $value =~ s/^\\\"(.*)\\\"$/$1/;\n        return $value;\n    } else {\n        die \"internal error\";\n    }\n}\n\nsub destroy_vm {\n    my ($storecfg, $vmid, $skiplock, $replacement_conf, $purge_unreferenced) = @_;\n\n    eval { PVE::QemuConfig::cleanup_fleecing_images($vmid, $storecfg) };\n    log_warn(\"attempt to clean up left-over fleecing images failed - $@\") if $@;\n\n    my $conf = PVE::QemuConfig->load_config($vmid);\n\n    if (!$skiplock && !PVE::QemuConfig->has_lock($conf, 'suspended')) {\n        PVE::QemuConfig->check_lock($conf);\n    }\n\n    if ($conf->{template}) {\n        # check if any base image is still used by a linked clone\n        PVE::QemuConfig->foreach_volume_full(\n            $conf,\n            { include_unused => 1 },\n            sub {\n                my ($ds, $drive) = @_;\n                return if drive_is_cdrom($drive);\n\n                my $volid = $drive->{file};\n                return if !$volid || $volid =~ m|^/|;\n\n                die \"base volume '$volid' is still in use by linked cloned\\n\"\n                    if PVE::Storage::volume_is_base_and_used($storecfg, $volid);\n\n            },\n        );\n    }\n\n    my $volids = {};\n    my $remove_owned_drive = sub {\n        my ($ds, $drive) = @_;\n        return if drive_is_cdrom($drive, 1);\n\n        my $volid = $drive->{file};\n        return if !$volid || $volid =~ m|^/|;\n        return if $volids->{$volid};\n\n        my ($path, $owner) = PVE::Storage::path($storecfg, $volid);\n        return if !$path || !$owner || ($owner != $vmid);\n\n        $volids->{$volid} = 1;\n        eval { PVE::Storage::vdisk_free($storecfg, $volid) };\n        warn \"Could not remove disk '$volid', check manually: $@\" if $@;\n    };\n\n    # only remove disks owned by this VM (referenced in the config)\n    my $include_opts = {\n        include_unused => 1,\n        extra_keys => ['vmstate'],\n    };\n    PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $remove_owned_drive);\n\n    for my $snap (values %{ $conf->{snapshots} }) {\n        next if !defined($snap->{vmstate});\n        my $drive = PVE::QemuConfig->parse_volume('vmstate', $snap->{vmstate}, 1);\n        next if !defined($drive);\n        $remove_owned_drive->('vmstate', $drive);\n    }\n\n    PVE::QemuConfig->foreach_volume_full($conf->{pending}, $include_opts, $remove_owned_drive);\n\n    if ($purge_unreferenced) { # also remove unreferenced disk\n        my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid, undef, 'images');\n        PVE::Storage::foreach_volid(\n            $vmdisks,\n            sub {\n                my ($volid, $sid, $volname, $d) = @_;\n                eval { PVE::Storage::vdisk_free($storecfg, $volid) };\n                warn $@ if $@;\n            },\n        );\n    }\n\n    eval { PVE::QemuServer::Network::delete_ifaces_ipams_ips($conf, $vmid) };\n    warn $@ if $@;\n\n    if (defined $replacement_conf) {\n        PVE::QemuConfig->write_config($vmid, $replacement_conf);\n    } else {\n        PVE::QemuConfig->destroy_config($vmid);\n    }\n}\n\nmy $fleecing_section_schema = {\n    'fleecing-images' => {\n        type => 'string',\n        format => 'pve-volume-id-list',\n        description => \"For internal use only. List of fleecing images allocated during backup.\"\n            . \" If no backup is running, these are left-overs that failed to be removed.\",\n        optional => 1,\n    },\n};\n\nsub parse_vm_config {\n    my ($filename, $raw, $strict) = @_;\n\n    return if !defined($raw);\n\n    # note that pending, snapshot and special sections are currently skipped when a backup is taken\n    my $res = {\n        digest => Digest::SHA::sha1_hex($raw),\n        snapshots => {},\n        pending => undef,\n        'special-sections' => {},\n    };\n\n    my $handle_error = sub {\n        my ($msg) = @_;\n\n        if ($strict) {\n            die $msg;\n        } else {\n            warn $msg;\n        }\n    };\n\n    $filename =~ m|/qemu-server/(\\d+)\\.conf$|\n        || die \"got strange filename '$filename'\";\n\n    my $vmid = $1;\n\n    my $conf = $res;\n    my $descr;\n    my $finish_description = sub {\n        if (defined($descr)) {\n            $descr =~ s/\\s+$//;\n            $conf->{description} = $descr;\n        }\n        $descr = undef;\n    };\n\n    my $special_schemas = {\n        cloudinit => $confdesc, # not actually used right now, see below\n        fleecing => $fleecing_section_schema,\n    };\n    my $special_sections_re_string = join('|', keys $special_schemas->%*);\n    my $special_sections_re_1 = qr/($special_sections_re_string)/;\n\n    my $section = { name => '', type => 'main', schema => $confdesc };\n\n    my @lines = split(/\\n/, $raw);\n    foreach my $line (@lines) {\n        next if $line =~ m/^\\s*$/;\n\n        if ($line =~ m/^\\[PENDING\\]\\s*$/i) {\n            $section = { name => 'pending', type => 'pending', schema => $confdesc };\n            $finish_description->();\n            $handle_error->(\"vm $vmid - duplicate section: $section->{name}\\n\")\n                if defined($res->{ $section->{name} });\n            $conf = $res->{ $section->{name} } = {};\n            next;\n        } elsif ($line =~ m/^\\[special:$special_sections_re_1\\]\\s*$/i) {\n            $section = { name => $1, type => 'special', schema => $special_schemas->{$1} };\n            $finish_description->();\n            $handle_error->(\"vm $vmid - duplicate special section: $section->{name}\\n\")\n                if defined($res->{'special-sections'}->{ $section->{name} });\n            $conf = $res->{'special-sections'}->{ $section->{name} } = {};\n            next;\n\n        } elsif ($line =~ m/^\\[([a-z][a-z0-9_\\-]+)\\]\\s*$/i) {\n            $section = { name => $1, type => 'snapshot', schema => $confdesc };\n            $finish_description->();\n            $handle_error->(\"vm $vmid - duplicate snapshot section: $section->{name}\\n\")\n                if defined($res->{snapshots}->{ $section->{name} });\n            $conf = $res->{snapshots}->{ $section->{name} } = {};\n            next;\n        } elsif ($line =~ m/^\\[([^\\]]*)\\]\\s*$/i) {\n            my $unknown_section = $1;\n            $section = undef;\n            $finish_description->();\n            $handle_error->(\"vm $vmid - skipping unknown section: '$unknown_section'\\n\");\n            next;\n        }\n\n        next if !defined($section);\n\n        if ($line =~ m/^\\#(.*)$/) {\n            $descr = '' if !defined($descr);\n            $descr .= PVE::Tools::decode_text($1) . \"\\n\";\n            next;\n        }\n\n        if ($line =~ m/^(description):\\s*(.*\\S)\\s*$/) {\n            $descr = '' if !defined($descr);\n            $descr .= PVE::Tools::decode_text($2);\n        } elsif ($line =~ m/snapstate:\\s*(prepare|delete)\\s*$/) {\n            $conf->{snapstate} = $1;\n        } elsif ($line =~ m/^(args):\\s*(.*\\S)\\s*$/) {\n            my $key = $1;\n            my $value = $2;\n            $conf->{$key} = $value;\n        } elsif ($line =~ m/^delete:\\s*(.*\\S)\\s*$/) {\n            my $value = $1;\n            if ($section->{name} eq 'pending' && $section->{type} eq 'pending') {\n                $conf->{delete} = $value; # we parse this later\n            } else {\n                $handle_error->(\"vm $vmid - property 'delete' is only allowed in [PENDING]\\n\");\n            }\n        } elsif ($line =~ m/^([a-z][a-z_\\-]*\\d*):\\s*(.+?)\\s*$/) {\n            my $key = $1;\n            my $value = $2;\n            if ($section->{name} eq 'cloudinit' && $section->{type} eq 'special') {\n                # ignore validation only used for informative purpose\n                $conf->{$key} = $value;\n                next;\n            }\n            eval { $value = check_type($key, $value, $section->{schema}); };\n            if ($@) {\n                $handle_error->(\"vm $vmid - unable to parse value of '$key' - $@\");\n            } else {\n                $key = 'ide2' if $key eq 'cdrom';\n                my $fmt = $section->{schema}->{$key}->{format};\n                if ($fmt && $fmt =~ /^pve-qm-(?:ide|scsi|virtio|sata)$/) {\n                    my $v = parse_drive($key, $value);\n                    if (my $volid = filename_to_volume_id($vmid, $v->{file}, $v->{media})) {\n                        $v->{file} = $volid;\n                        $value = print_drive($v);\n                    } else {\n                        $handle_error->(\"vm $vmid - unable to parse value of '$key'\\n\");\n                        next;\n                    }\n                }\n\n                $conf->{$key} = $value;\n            }\n        } else {\n            $handle_error->(\"vm $vmid - unable to parse config: $line\\n\");\n        }\n    }\n\n    $finish_description->();\n    delete $res->{snapstate}; # just to be sure\n\n    $res->{pending} = {} if !defined($res->{pending});\n\n    return $res;\n}\n\nsub write_vm_config {\n    my ($filename, $conf) = @_;\n\n    delete $conf->{snapstate}; # just to be sure\n\n    if ($conf->{cdrom}) {\n        die \"option ide2 conflicts with cdrom\\n\" if $conf->{ide2};\n        $conf->{ide2} = $conf->{cdrom};\n        delete $conf->{cdrom};\n    }\n\n    # we do not use 'smp' any longer\n    if ($conf->{sockets}) {\n        delete $conf->{smp};\n    } elsif ($conf->{smp}) {\n        $conf->{sockets} = $conf->{smp};\n        delete $conf->{cores};\n        delete $conf->{smp};\n    }\n\n    my $used_volids = {};\n\n    my $cleanup_config = sub {\n        my ($cref, $pending, $snapname) = @_;\n\n        foreach my $key (keys %$cref) {\n            next\n                if $key eq 'digest'\n                || $key eq 'description'\n                || $key eq 'snapshots'\n                || $key eq 'snapstate'\n                || $key eq 'pending'\n                || $key eq 'special-sections';\n            my $value = $cref->{$key};\n            if ($key eq 'delete') {\n                die \"propertry 'delete' is only allowed in [PENDING]\\n\"\n                    if !$pending;\n                # fixme: check syntax?\n                next;\n            }\n            eval { $value = check_type($key, $value, $confdesc); };\n            die \"unable to parse value of '$key' - $@\" if $@;\n\n            $cref->{$key} = $value;\n\n            if (!$snapname && is_valid_drivename($key)) {\n                my $drive = parse_drive($key, $value);\n                $used_volids->{ $drive->{file} } = 1 if $drive && $drive->{file};\n            }\n        }\n    };\n\n    &$cleanup_config($conf);\n\n    &$cleanup_config($conf->{pending}, 1);\n\n    foreach my $snapname (keys %{ $conf->{snapshots} }) {\n        die \"internal error: snapshot name '$snapname' is forbidden\" if lc($snapname) eq 'pending';\n        &$cleanup_config($conf->{snapshots}->{$snapname}, undef, $snapname);\n    }\n\n    # remove 'unusedX' settings if we re-add a volume\n    foreach my $key (keys %$conf) {\n        my $value = $conf->{$key};\n        if ($key =~ m/^unused/ && $used_volids->{$value}) {\n            delete $conf->{$key};\n        }\n    }\n\n    my $generate_raw_config = sub {\n        my ($conf, $pending) = @_;\n\n        my $raw = '';\n\n        # add description as comment to top of file\n        if (defined(my $descr = $conf->{description})) {\n            if ($descr) {\n                foreach my $cl (split(/\\n/, $descr)) {\n                    $raw .= '#' . PVE::Tools::encode_text($cl) . \"\\n\";\n                }\n            } else {\n                $raw .= \"#\\n\" if $pending;\n            }\n        }\n\n        foreach my $key (sort keys %$conf) {\n            next if $key =~ /^(digest|description|pending|snapshots|special-sections)$/;\n            $raw .= \"$key: $conf->{$key}\\n\";\n        }\n        return $raw;\n    };\n\n    my $raw = &$generate_raw_config($conf);\n\n    if (scalar(keys %{ $conf->{pending} })) {\n        $raw .= \"\\n[PENDING]\\n\";\n        $raw .= &$generate_raw_config($conf->{pending}, 1);\n    }\n\n    for my $special (sort keys $conf->{'special-sections'}->%*) {\n        next if $special eq 'cloudinit' && !PVE::QemuConfig->has_cloudinit($conf);\n        $raw .= \"\\n[special:$special]\\n\";\n        $raw .= &$generate_raw_config($conf->{'special-sections'}->{$special});\n    }\n\n    foreach my $snapname (sort keys %{ $conf->{snapshots} }) {\n        $raw .= \"\\n[$snapname]\\n\";\n        $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});\n    }\n\n    return $raw;\n}\n\nsub get_default_property_value {\n    my ($name) = @_;\n\n    return $confdesc->{$name}->{default};\n}\n\nsub load_defaults {\n\n    my $res = {};\n\n    # we use static defaults from our JSON schema configuration\n    foreach my $key (keys %$confdesc) {\n        if (defined(my $default = $confdesc->{$key}->{default})) {\n            $res->{$key} = $default;\n        }\n    }\n\n    return $res;\n}\n\nsub config_list {\n    my $vmlist = PVE::Cluster::get_vmlist();\n    my $res = {};\n    return $res if !$vmlist || !$vmlist->{ids};\n    my $ids = $vmlist->{ids};\n    my $nodename = nodename();\n\n    foreach my $vmid (keys %$ids) {\n        my $d = $ids->{$vmid};\n        next if !$d->{node} || $d->{node} ne $nodename;\n        next if !$d->{type} || $d->{type} ne 'qemu';\n        $res->{$vmid}->{exists} = 1;\n    }\n    return $res;\n}\n\n# check if used storages are available on all nodes (use by migrate)\nsub check_storage_availability {\n    my ($storecfg, $conf, $node) = @_;\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            my $volid = $drive->{file};\n            return if !$volid;\n\n            my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n            return if !$sid;\n\n            # check if storage is available on both nodes\n            my $scfg = PVE::Storage::storage_check_enabled($storecfg, $sid);\n            PVE::Storage::storage_check_enabled($storecfg, $sid, $node);\n\n            my ($vtype) = PVE::Storage::parse_volname($storecfg, $volid);\n\n            die \"$volid: content type '$vtype' is not available on storage '$sid'\\n\"\n                if !$scfg->{content}->{$vtype};\n        },\n    );\n}\n\n# list nodes where all VM images are available (used by has_feature API)\nsub shared_nodes {\n    my ($conf, $storecfg) = @_;\n\n    my $nodelist = PVE::Cluster::get_nodelist();\n    my $nodehash = { map { $_ => 1 } @$nodelist };\n    my $nodename = nodename();\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            my $volid = $drive->{file};\n            return if !$volid;\n\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n            if ($storeid) {\n                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n                if ($scfg->{disable}) {\n                    $nodehash = {};\n                } elsif (my $avail = $scfg->{nodes}) {\n                    foreach my $node (keys %$nodehash) {\n                        delete $nodehash->{$node} if !$avail->{$node};\n                    }\n                } elsif (!$scfg->{shared}) {\n                    foreach my $node (keys %$nodehash) {\n                        delete $nodehash->{$node} if $node ne $nodename;\n                    }\n                }\n            }\n        },\n    );\n\n    return $nodehash;\n}\n\nsub check_local_storage_availability {\n    my ($conf, $storecfg) = @_;\n\n    my $nodelist = PVE::Cluster::get_nodelist();\n    my $nodehash = { map { $_ => {} } @$nodelist };\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            my $volid = $drive->{file};\n            return if !$volid;\n\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n            if ($storeid) {\n                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n\n                if ($scfg->{disable}) {\n                    foreach my $node (keys %$nodehash) {\n                        $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;\n                    }\n                } elsif (my $avail = $scfg->{nodes}) {\n                    foreach my $node (keys %$nodehash) {\n                        if (!$avail->{$node}) {\n                            $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;\n                        }\n                    }\n                }\n            }\n        },\n    );\n\n    foreach my $node (values %$nodehash) {\n        if (my $unavail = $node->{unavailable_storages}) {\n            $node->{unavailable_storages} = [sort keys %$unavail];\n        }\n    }\n\n    return $nodehash;\n}\n\n# Compat only, use assert_config_exists_on_node and vm_running_locally where possible\nsub check_running {\n    my ($vmid, $nocheck, $node) = @_;\n\n    # $nocheck is set when called during a migration, in which case the config\n    # file might still or already reside on the *other* node\n    # - because rename has already happened, and current node is source\n    # - because rename hasn't happened yet, and current node is target\n    # - because rename has happened, current node is target, but hasn't yet\n    # processed it yet\n    PVE::QemuConfig::assert_config_exists_on_node($vmid, $node) if !$nocheck;\n    return PVE::QemuServer::Helpers::vm_running_locally($vmid);\n}\n\nsub vzlist {\n\n    my $vzlist = config_list();\n\n    my $fd = IO::Dir->new($PVE::QemuServer::Helpers::var_run_tmpdir) || return $vzlist;\n\n    while (defined(my $de = $fd->read)) {\n        next if $de !~ m/^(\\d+)\\.pid$/;\n        my $vmid = $1;\n        next if !defined($vzlist->{$vmid});\n        if (my $pid = check_running($vmid)) {\n            $vzlist->{$vmid}->{pid} = $pid;\n        }\n    }\n\n    return $vzlist;\n}\n\nour $vmstatus_return_properties = {\n    vmid => get_standard_option('pve-vmid'),\n    status => {\n        description => \"QEMU process status.\",\n        type => 'string',\n        enum => ['stopped', 'running'],\n    },\n    mem => {\n        description => \"Currently used memory in bytes. Does not take into account kernel\"\n            . \" same-page merging (KSM). Uses information from ballooning when available.\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    maxmem => {\n        description => \"Maximum memory in bytes.\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    memhost => {\n        description => \"Current memory usage on the host. Does not take into account kernel\"\n            . \" same-page merging (KSM).\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    maxdisk => {\n        description => \"Root disk size in bytes.\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    diskread => {\n        description =>\n            \"The amount of bytes the guest read from it's block devices since the guest\"\n            . \" was started. (Note: This info is not available for all storage types.)\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    diskwrite => {\n        description =>\n            \"The amount of bytes the guest wrote from it's block devices since the guest\"\n            . \" was started. (Note: This info is not available for all storage types.)\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    name => {\n        description => \"VM (host)name.\",\n        type => 'string',\n        optional => 1,\n    },\n    netin => {\n        description =>\n            \"The amount of traffic in bytes that was sent to the guest over the network\"\n            . \" since it was started.\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    netout => {\n        description =>\n            \"The amount of traffic in bytes that was sent from the guest over the network\"\n            . \" since it was started.\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'bytes',\n    },\n    qmpstatus => {\n        description => \"VM run state from the 'query-status' QMP monitor command.\",\n        type => 'string',\n        optional => 1,\n    },\n    pid => {\n        description => \"PID of the QEMU process, if the VM is running.\",\n        type => 'integer',\n        optional => 1,\n    },\n    uptime => {\n        description => \"Uptime in seconds.\",\n        type => 'integer',\n        optional => 1,\n        renderer => 'duration',\n    },\n    cpu => {\n        description => \"Current CPU usage.\",\n        type => 'number',\n        optional => 1,\n    },\n    cpus => {\n        description => \"Maximum usable CPUs.\",\n        type => 'number',\n        optional => 1,\n    },\n    lock => {\n        description => \"The current config lock, if any.\",\n        type => 'string',\n        optional => 1,\n    },\n    tags => {\n        description => \"The current configured tags, if any\",\n        type => 'string',\n        optional => 1,\n    },\n    'running-machine' => {\n        description => \"The currently running machine type (if running).\",\n        type => 'string',\n        optional => 1,\n    },\n    'running-qemu' => {\n        description => \"The QEMU version the VM is currently using (if running).\",\n        type => 'string',\n        optional => 1,\n    },\n    template => {\n        description => \"Determines if the guest is a template.\",\n        type => 'boolean',\n        optional => 1,\n        default => 0,\n    },\n    serial => {\n        description => \"Guest has serial device configured.\",\n        type => 'boolean',\n        optional => 1,\n    },\n    pressurecpusome => {\n        description => \"CPU Some pressure stall average over the last 10 seconds.\",\n        type => 'number',\n        optional => 1,\n    },\n    pressurecpufull => {\n        description => \"CPU Full pressure stall average over the last 10 seconds.\",\n        type => 'number',\n        optional => 1,\n    },\n    pressureiosome => {\n        description => \"IO Some pressure stall average over the last 10 seconds.\",\n        type => 'number',\n        optional => 1,\n    },\n    pressureiofull => {\n        description => \"IO Full pressure stall average over the last 10 seconds.\",\n        type => 'number',\n        optional => 1,\n    },\n    pressurememorysome => {\n        description => \"Memory Some pressure stall average over the last 10 seconds.\",\n        type => 'number',\n        optional => 1,\n    },\n    pressurememoryfull => {\n        description => \"Memory Full pressure stall average over the last 10 seconds.\",\n        type => 'number',\n        optional => 1,\n    },\n};\n\nmy $last_proc_pid_stat;\n\n# get VM status information\n# This must be fast and should not block ($full == false)\n# We only query KVM using QMP if $full == true (this can be slow)\nsub vmstatus {\n    my ($opt_vmid, $full) = @_;\n\n    my $res = {};\n\n    my $storecfg = PVE::Storage::config();\n\n    my $list = vzlist();\n    my $defaults = load_defaults();\n\n    my ($uptime) = PVE::ProcFSTools::read_proc_uptime(1);\n\n    my $cpucount = $cpuinfo->{cpus} || 1;\n\n    foreach my $vmid (keys %$list) {\n        next if $opt_vmid && ($vmid ne $opt_vmid);\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        my $d = { vmid => int($vmid) };\n        $d->{pid} = int($list->{$vmid}->{pid}) if $list->{$vmid}->{pid};\n\n        # fixme: better status?\n        $d->{status} = $list->{$vmid}->{pid} ? 'running' : 'stopped';\n\n        my $size = PVE::QemuServer::Drive::bootdisk_size($storecfg, $conf);\n        if (defined($size)) {\n            $d->{disk} = 0; # no info available\n            $d->{maxdisk} = $size;\n        } else {\n            $d->{disk} = 0;\n            $d->{maxdisk} = 0;\n        }\n\n        $d->{cpus} =\n            ($conf->{sockets} || $defaults->{sockets}) * ($conf->{cores} || $defaults->{cores});\n        $d->{cpus} = $cpucount if $d->{cpus} > $cpucount;\n        $d->{cpus} = $conf->{vcpus} if $conf->{vcpus};\n\n        $d->{name} = $conf->{name} || \"VM $vmid\";\n        $d->{maxmem} = get_current_memory($conf->{memory}) * (1024 * 1024);\n\n        if ($conf->{balloon}) {\n            $d->{balloon_min} = $conf->{balloon} * (1024 * 1024);\n            $d->{shares} =\n                defined($conf->{shares})\n                ? $conf->{shares}\n                : $defaults->{shares};\n        }\n\n        $d->{uptime} = 0;\n        $d->{cpu} = 0;\n        $d->{mem} = 0;\n        $d->{memhost} = 0;\n\n        $d->{netout} = 0;\n        $d->{netin} = 0;\n\n        $d->{template} = 1 if PVE::QemuConfig->is_template($conf);\n\n        $d->{serial} = 1 if conf_has_serial($conf);\n        $d->{lock} = $conf->{lock} if $conf->{lock};\n        $d->{tags} = $conf->{tags} if defined($conf->{tags});\n\n        $res->{$vmid} = $d;\n    }\n\n    my $netdev = PVE::ProcFSTools::read_proc_net_dev();\n    foreach my $dev (keys %$netdev) {\n        next if $dev !~ m/^tap([1-9]\\d*)i/;\n        my $vmid = $1;\n        my $d = $res->{$vmid};\n        next if !$d;\n\n        $d->{netout} += $netdev->{$dev}->{receive};\n        $d->{netin} += $netdev->{$dev}->{transmit};\n\n        if ($full) {\n            $d->{nics}->{$dev}->{netout} = int($netdev->{$dev}->{receive});\n            $d->{nics}->{$dev}->{netin} = int($netdev->{$dev}->{transmit});\n        }\n\n    }\n\n    my $ctime = gettimeofday;\n\n    foreach my $vmid (keys %$list) {\n\n        my $d = $res->{$vmid};\n        my $pid = $d->{pid};\n        next if !$pid;\n\n        my $pstat = PVE::ProcFSTools::read_proc_pid_stat($pid);\n        next if !$pstat; # not running\n\n        my $used = $pstat->{utime} + $pstat->{stime};\n\n        $d->{uptime} = int(($uptime - $pstat->{starttime}) / $cpuinfo->{user_hz});\n\n        my $cgroup = PVE::QemuServer::CGroup->new($vmid);\n        my $cgroup_mem = eval { $cgroup->get_memory_stat() } // {};\n        warn \"unable to get memory stat for $vmid - $@\" if $@;\n        $d->{memhost} = $cgroup_mem->{mem} // 0;\n\n        $d->{mem} = $d->{memhost}; # default to cgroup, balloon info can override this below\n\n        my $pressures = PVE::ProcFSTools::read_cgroup_pressure(\"qemu.slice/${vmid}.scope\");\n        $d->{pressurecpusome} = $pressures->{cpu}->{some}->{avg10} * 1;\n        $d->{pressurecpufull} = $pressures->{cpu}->{full}->{avg10} * 1;\n        $d->{pressureiosome} = $pressures->{io}->{some}->{avg10} * 1;\n        $d->{pressureiofull} = $pressures->{io}->{full}->{avg10} * 1;\n        $d->{pressurememorysome} = $pressures->{memory}->{some}->{avg10} * 1;\n        $d->{pressurememoryfull} = $pressures->{memory}->{full}->{avg10} * 1;\n\n        my $old = $last_proc_pid_stat->{$pid};\n        if (!$old) {\n            $last_proc_pid_stat->{$pid} = {\n                time => $ctime,\n                used => $used,\n                cpu => 0,\n            };\n            next;\n        }\n\n        my $dtime = ($ctime - $old->{time}) * $cpucount * $cpuinfo->{user_hz};\n\n        if ($dtime > 1000) {\n            my $dutime = $used - $old->{used};\n\n            $d->{cpu} = (($dutime / $dtime) * $cpucount) / $d->{cpus};\n            $last_proc_pid_stat->{$pid} = {\n                time => $ctime,\n                used => $used,\n                cpu => $d->{cpu},\n            };\n        } else {\n            $d->{cpu} = $old->{cpu};\n        }\n    }\n\n    return $res if !$full;\n\n    my $qmpclient = PVE::QMPClient->new();\n\n    my $ballooncb = sub {\n        my ($vmid, $resp) = @_;\n\n        my $info = $resp->{'return'};\n        return if !$info->{max_mem};\n\n        my $d = $res->{$vmid};\n\n        # use memory assigned to VM\n        $d->{maxmem} = $info->{max_mem};\n        $d->{balloon} = $info->{actual};\n\n        if (defined($info->{total_mem}) && defined($info->{free_mem})) {\n            $d->{mem} = $info->{total_mem} - $info->{free_mem};\n            $d->{freemem} = $info->{free_mem};\n        }\n\n        $d->{ballooninfo} = $info;\n    };\n\n    my $blockstatscb = sub {\n        my ($vmid, $resp) = @_;\n        my $data = $resp->{'return'} || [];\n        my $totalrdbytes = 0;\n        my $totalwrbytes = 0;\n\n        for my $blockstat (@$data) {\n            $totalrdbytes = $totalrdbytes + $blockstat->{stats}->{rd_bytes};\n            $totalwrbytes = $totalwrbytes + $blockstat->{stats}->{wr_bytes};\n\n            # With the switch to -blockdev, the block backend in QEMU has no name, so need to also\n            # consider the qdev ID. Note that empty CD-ROM drives do not have a node name, so that\n            # cannot be used as a fallback instead of the qdev ID.\n            my $drive_id;\n            if ($blockstat->{device}) {\n                $drive_id = $blockstat->{device} =~ s/drive-//r;\n            } elsif ($blockstat->{qdev}) {\n                $drive_id = PVE::QemuServer::Blockdev::qdev_id_to_drive_id($blockstat->{qdev});\n            } else {\n                print \"blockstats callback: unexpected missing drive ID\\n\";\n                next;\n            }\n\n            $res->{$vmid}->{blockstat}->{$drive_id} = $blockstat->{stats};\n        }\n        $res->{$vmid}->{diskread} = $totalrdbytes;\n        $res->{$vmid}->{diskwrite} = $totalwrbytes;\n    };\n\n    my $machinecb = sub {\n        my ($vmid, $resp) = @_;\n        my $data = $resp->{'return'} || [];\n\n        $res->{$vmid}->{'running-machine'} =\n            PVE::QemuServer::Machine::current_from_query_machines($data);\n    };\n\n    my $versioncb = sub {\n        my ($vmid, $resp) = @_;\n        my $data = $resp->{'return'} // {};\n        my $version = 'unknown';\n\n        if (my $v = $data->{qemu}) {\n            $version = $v->{major} . \".\" . $v->{minor} . \".\" . $v->{micro};\n        }\n\n        $res->{$vmid}->{'running-qemu'} = $version;\n    };\n\n    my $proxmox_support_cb = sub {\n        my ($vmid, $resp) = @_;\n        $res->{$vmid}->{'proxmox-support'} = $resp->{'return'} // {};\n    };\n\n    my $statuscb = sub {\n        my ($vmid, $resp) = @_;\n\n        my $qmp_peer = vm_qmp_peer($vmid);\n\n        $qmpclient->queue_cmd($qmp_peer, $proxmox_support_cb, 'query-proxmox-support');\n        $qmpclient->queue_cmd($qmp_peer, $blockstatscb, 'query-blockstats');\n        $qmpclient->queue_cmd($qmp_peer, $machinecb, 'query-machines');\n        $qmpclient->queue_cmd($qmp_peer, $versioncb, 'query-version');\n        # this fails if balloon driver is not loaded, so this must be\n        # the last command (following command are aborted if this fails).\n        $qmpclient->queue_cmd($qmp_peer, $ballooncb, 'query-balloon');\n\n        my $status = 'unknown';\n        if (!defined($status = $resp->{'return'}->{status})) {\n            warn \"unable to get VM status\\n\";\n            return;\n        }\n\n        $res->{$vmid}->{qmpstatus} = $resp->{'return'}->{status};\n    };\n\n    foreach my $vmid (keys %$list) {\n        next if $opt_vmid && ($vmid ne $opt_vmid);\n        next if !$res->{$vmid}->{pid}; # not running\n        $qmpclient->queue_cmd(vm_qmp_peer($vmid), $statuscb, 'query-status');\n    }\n\n    $qmpclient->queue_execute(undef, 2);\n\n    foreach my $vmid (keys %$list) {\n        next if $opt_vmid && ($vmid ne $opt_vmid);\n        $res->{$vmid}->{qmpstatus} = $res->{$vmid}->{status} if !$res->{$vmid}->{qmpstatus};\n    }\n\n    return $res;\n}\n\nsub conf_has_serial {\n    my ($conf) = @_;\n\n    for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {\n        if ($conf->{\"serial$i\"}) {\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\nsub conf_has_audio {\n    my ($conf, $id) = @_;\n\n    $id //= 0;\n    my $audio = $conf->{\"audio$id\"};\n    return if !defined($audio);\n\n    my $audioproperties = parse_property_string($audio_fmt, $audio);\n    my $audiodriver = $audioproperties->{driver} // 'spice';\n\n    return {\n        dev => $audioproperties->{device},\n        dev_id => \"audiodev$id\",\n        backend => $audiodriver,\n        backend_id => \"$audiodriver-backend${id}\",\n    };\n}\n\nsub audio_devs {\n    my ($audio, $audiopciaddr, $machine_version) = @_;\n\n    my $devs = [];\n\n    my $id = $audio->{dev_id};\n    my $audiodev = \"\";\n    if (min_version($machine_version, 4, 2)) {\n        $audiodev = \",audiodev=$audio->{backend_id}\";\n    }\n\n    if ($audio->{dev} eq 'AC97') {\n        push @$devs, '-device', \"AC97,id=${id}${audiopciaddr}$audiodev\";\n    } elsif ($audio->{dev} =~ /intel\\-hda$/) {\n        push @$devs, '-device', \"$audio->{dev},id=${id}${audiopciaddr}\";\n        push @$devs, '-device', \"hda-micro,id=${id}-codec0,bus=${id}.0,cad=0$audiodev\";\n        push @$devs, '-device', \"hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1$audiodev\";\n    } else {\n        die \"unknown audio device '$audio->{dev}', implement me!\";\n    }\n\n    push @$devs, '-audiodev', \"$audio->{backend},id=$audio->{backend_id}\";\n\n    return $devs;\n}\n\nsub get_tpm_paths {\n    my ($vmid) = @_;\n    return {\n        socket => \"/var/run/qemu-server/$vmid.swtpm\",\n        pid => \"/var/run/qemu-server/$vmid.swtpm.pid\",\n    };\n}\n\nsub add_tpm_device {\n    my ($vmid, $devices, $conf) = @_;\n\n    return if !$conf->{tpmstate0};\n\n    my $paths = get_tpm_paths($vmid);\n\n    push @$devices, \"-chardev\", \"socket,id=tpmchar,path=$paths->{socket}\";\n    push @$devices, \"-tpmdev\", \"emulator,id=tpmdev,chardev=tpmchar\";\n    push @$devices, \"-device\", \"tpm-tis,tpmdev=tpmdev\";\n}\n\nsub start_swtpm {\n    my ($storecfg, $vmid, $tpmdrive, $migration) = @_;\n\n    return if !$tpmdrive;\n\n    my $state;\n    my $tpm = parse_drive(\"tpmstate0\", $tpmdrive);\n    my ($storeid) = PVE::Storage::parse_volume_id($tpm->{file}, 1);\n    if ($storeid) {\n        if (PVE::QemuServer::Drive::drive_uses_qsd_fuse($storecfg, $tpm)) {\n            PVE::QemuServer::QSD::start($vmid);\n            $state = PVE::QemuServer::QSD::add_fuse_export($vmid, $tpm, 'tpmstate0');\n        } else {\n            $state = PVE::Storage::map_volume($storecfg, $tpm->{file});\n        }\n    } else {\n        $state = $tpm->{file};\n    }\n\n    my $paths = get_tpm_paths($vmid);\n\n    # during migration, we will get state from remote\n    #\n    if (!$migration) {\n        # run swtpm_setup to create a new TPM state if it doesn't exist yet\n        my $setup_cmd = [\n            \"swtpm_setup\",\n            \"--tpmstate\",\n            \"file://$state\",\n            \"--createek\",\n            \"--create-ek-cert\",\n            \"--create-platform-cert\",\n            \"--lock-nvram\",\n            \"--config\",\n            \"/etc/swtpm_setup.conf\", # do not use XDG configs\n            \"--runas\",\n            \"0\", # force creation as root, error if not possible\n            \"--not-overwrite\", # ignore existing state, do not modify\n        ];\n\n        push @$setup_cmd, \"--tpm2\" if $tpm->{version} && $tpm->{version} eq 'v2.0';\n        # TPM 2.0 supports ECC crypto, use if possible\n        push @$setup_cmd, \"--ecc\" if $tpm->{version} && $tpm->{version} eq 'v2.0';\n\n        run_command(\n            $setup_cmd,\n            outfunc => sub {\n                print \"swtpm_setup: $1\\n\";\n            },\n        );\n    }\n\n    # Used to distinguish different invocations in the log.\n    my $log_prefix = \"[id=\" . int(time()) . \"] \";\n\n    my $emulator_cmd = [\n        \"swtpm\",\n        \"socket\",\n        \"--tpmstate\",\n        \"backend-uri=file://$state,mode=0600\",\n        \"--ctrl\",\n        \"type=unixio,path=$paths->{socket},mode=0600\",\n        \"--pid\",\n        \"file=$paths->{pid}\",\n        \"--terminate\", # terminate on QEMU disconnect\n        \"--daemon\",\n        \"--log\",\n        \"file=/run/qemu-server/$vmid-swtpm.log,level=1,prefix=$log_prefix\",\n    ];\n    push @$emulator_cmd, \"--tpm2\" if $tpm->{version} && $tpm->{version} eq 'v2.0';\n    run_command($emulator_cmd, outfunc => sub { print $1; });\n\n    my $tries = 100; # swtpm may take a bit to start before daemonizing, wait up to 5s for pid\n    while (!-e $paths->{pid}) {\n        die \"failed to start swtpm: pid file '$paths->{pid}' wasn't created.\\n\" if --$tries == 0;\n        usleep(50_000);\n    }\n\n    # return untainted PID of swtpm daemon so it can be killed on error\n    file_read_firstline($paths->{pid}) =~ m/(\\d+)/;\n    return $1;\n}\n\nsub vga_conf_has_spice {\n    my ($vga) = @_;\n\n    my $vgaconf = parse_vga($vga);\n    my $vgatype = $vgaconf->{type};\n    return 0 if !$vgatype || $vgatype !~ m/^qxl([234])?$/;\n\n    return $1 || 1;\n}\n\n# To use query_supported_cpu_flags and query_understood_cpu_flags to get flags\n# to use in a QEMU command line (-cpu element), first array_intersect the result\n# of query_supported_ with query_understood_. This is necessary because:\n#\n# a) query_understood_ returns flags the host cannot use and\n# b) query_supported_ (rather the QMP call) doesn't actually return CPU\n#    flags, but CPU settings - with most of them being flags. Those settings\n#    (and some flags, curiously) cannot be specified as a \"-cpu\" argument.\n#\n# query_supported_ needs to start up to 2 temporary VMs and is therefore rather\n# expensive. If you need the value returned from this, you can get it much\n# cheaper from pmxcfs using PVE::Cluster::get_node_kv('cpuflags-$accel') with\n# $accel being 'kvm' or 'tcg'.\n#\n# pvestatd calls this function on startup and whenever the QEMU/KVM version\n# changes, automatically populating pmxcfs.\n#\n# Returns: { kvm => [ flagX, flagY, ... ], tcg => [ flag1, flag2, ... ] }\n# since kvm and tcg machines support different flags\n#\nsub query_supported_cpu_flags {\n    my ($arch) = @_;\n\n    my $host_arch = get_host_arch();\n    $arch //= $host_arch;\n    my $default_machine = PVE::QemuServer::Machine::default_machine_for_arch($arch);\n\n    my $flags = {};\n\n    my $kvm_supported = defined(kvm_version()) && $arch eq $host_arch;\n    my $qemu_cmd = PVE::QemuServer::Helpers::get_command_for_arch($arch);\n    my $fakevmid = -1;\n    my $pidfile = PVE::QemuServer::Helpers::vm_pidfile_name($fakevmid);\n\n    # Start a temporary (frozen) VM with vmid -1 to allow sending a QMP command\n    my $query_supported_run_qemu = sub {\n        my ($kvm) = @_;\n\n        my $flags = {};\n        my $cmd = [\n            $qemu_cmd,\n            '-machine',\n            $default_machine,\n            '-display',\n            'none',\n            '-chardev',\n            \"socket,id=qmp,path=/var/run/qemu-server/$fakevmid.qmp,server=on,wait=off\",\n            '-mon',\n            'chardev=qmp,mode=control',\n            '-pidfile',\n            $pidfile,\n            '-S',\n            '-daemonize',\n        ];\n\n        if (!$kvm) {\n            push @$cmd, '-accel', 'tcg';\n        } else {\n            push @$cmd, '-cpu', 'host';\n        }\n\n        my $rc = run_command($cmd, noerr => 1, quiet => 0);\n        die \"QEMU flag querying VM exited with code \" . $rc if $rc;\n\n        eval {\n            my $cmd_result = mon_cmd(\n                $fakevmid,\n                'query-cpu-model-expansion',\n                type => 'full',\n                model => { name => $kvm ? 'host' : 'max' },\n            );\n\n            my $props = $cmd_result->{model}->{props};\n            foreach my $prop (keys %$props) {\n                next if $props->{$prop} ne '1';\n                # QEMU returns some flags multiple times, with '_', '.' or '-'\n                # (e.g. lahf_lm and lahf-lm; sse4.2, sse4-2 and sse4_2; ...).\n                # We only keep those with underscores, to match /proc/cpuinfo\n                $prop =~ s/\\.|-/_/g;\n                $flags->{$prop} = 1;\n            }\n        };\n        my $err = $@;\n\n        # force stop with 10 sec timeout and 'nocheck', always stop, even if QMP failed\n        vm_stop(undef, $fakevmid, 1, 1, 10, 0, 1);\n\n        die $err if $err;\n\n        return [sort keys %$flags];\n    };\n\n    # We need to query QEMU twice, since KVM and TCG have different supported flags\n    PVE::QemuConfig->lock_config(\n        $fakevmid,\n        sub {\n            $flags->{tcg} = eval { $query_supported_run_qemu->(0) };\n            warn \"warning: failed querying supported tcg flags: $@\\n\" if $@;\n\n            if ($kvm_supported) {\n                $flags->{kvm} = eval { $query_supported_run_qemu->(1) };\n                warn \"warning: failed querying supported kvm flags: $@\\n\" if $@;\n            }\n        },\n    );\n\n    return $flags;\n}\n\n# Understood CPU flags are written to a file at 'pve-qemu' compile time\nmy $understood_cpu_flag_dir = \"/usr/share/kvm\";\n\nsub query_understood_cpu_flags {\n    my $arch = get_host_arch();\n    my $filepath = \"$understood_cpu_flag_dir/recognized-CPUID-flags-$arch\";\n\n    die \"Cannot query understood QEMU CPU flags for architecture: $arch (file not found)\\n\"\n        if !-e $filepath;\n\n    my $raw = file_get_contents($filepath);\n    $raw =~ s/^\\s+|\\s+$//g;\n    my @flags = split(/\\s+/, $raw);\n\n    return \\@flags;\n}\n\n# Since commit 277d33454f77ec1d1e0bc04e37621e4dd2424b67 in pve-qemu, smm is not off by default\n# anymore. But smm=off seems to be required when using SeaBIOS and serial display.\nmy sub should_disable_smm {\n    my ($conf, $vga, $machine) = @_;\n\n    return if $machine =~ m/^virt/; # there is no smm flag that could be disabled\n\n    return\n        (!defined($conf->{bios}) || $conf->{bios} eq 'seabios')\n        && $vga->{type}\n        && $vga->{type} =~ m/^(serial\\d+|none)$/;\n}\n\nmy sub get_vga_properties {\n    my ($conf, $arch, $machine_version, $winversion) = @_;\n\n    my $vga = parse_vga($conf->{vga});\n\n    my $qxlnum = vga_conf_has_spice($conf->{vga});\n    $vga->{type} = 'qxl' if $qxlnum;\n\n    if (!$vga->{type}) {\n        if ($arch eq 'aarch64') {\n            $vga->{type} = 'virtio';\n        } elsif (min_version($machine_version, 2, 9)) {\n            $vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';\n        } else {\n            $vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';\n        }\n    }\n\n    return ($vga, $qxlnum);\n}\n\nsub config_to_command {\n    my ($storecfg, $vmid, $conf, $defaults, $options) = @_;\n\n    my ($forcemachine, $forcecpu, $live_restore_backing, $dry_run) =\n        $options->@{qw(force-machine force-cpu live-restore-backing dry-run)};\n\n    my $is_template = PVE::QemuConfig->is_template($conf);\n\n    # minimize config for templates, they can only start for backup,\n    # so most options besides the disks are irrelevant\n    if ($is_template) {\n        my $newconf = {\n            template => 1, # in case below code checks that\n            kvm => 0, # to prevent an error on hosts without virtualization extensions\n            vga => 'none', # to not start a vnc server\n            scsihw => $conf->{scsihw}, # so that the scsi disks are correctly added\n            bios => $conf->{bios}, # so efidisk gets included if it exists\n            name => $conf->{name}, # so it's correct in the process list\n        };\n\n        # copy all disks over\n        for my $device (PVE::QemuServer::Drive::valid_drive_names()) {\n            $newconf->{$device} = $conf->{$device};\n        }\n\n        # remaining configs stay default\n\n        # mark config to prevent writing it out\n        PVE::QemuConfig::NoWrite->mark_config($newconf);\n\n        $conf = $newconf;\n    }\n\n    my ($machineFlags, $rtcFlags) = ([], []);\n    my $devices = [];\n    my $bridges = {};\n    my $ostype = $conf->{ostype};\n    my $winversion = windows_version($ostype);\n    my $kvm = $conf->{kvm};\n    my $nodename = nodename();\n\n    my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine});\n\n    my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n    my $kvm_binary = PVE::QemuServer::Helpers::get_command_for_arch($arch);\n    my $kvmver = kvm_user_version($kvm_binary);\n\n    if (!$kvmver || $kvmver !~ m/^(\\d+)\\.(\\d+)/ || $1 < 6) {\n        $kvmver //= \"undefined\";\n        die \"Detected old QEMU binary ('$kvmver', at least 6.0 is required)\\n\";\n    }\n\n    my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf, $forcemachine);\n    my $machine_version = extract_version($machine_type, $kvmver);\n    $kvm //= 1 if is_native_arch($arch);\n\n    $machine_version =~ m/(\\d+)\\.(\\d+)/;\n    my ($machine_major, $machine_minor) = ($1, $2);\n\n    if ($kvmver =~ m/^\\d+\\.\\d+\\.(\\d+)/ && $1 >= 90) {\n        warn\n            \"warning: Installed QEMU version ($kvmver) is a release candidate, ignoring version checks\\n\";\n    } elsif (!min_version($kvmver, $machine_major, $machine_minor)) {\n        die \"Installed QEMU version '$kvmver' is too old to run machine type '$machine_type',\"\n            . \" please upgrade node '$nodename'\\n\";\n    } elsif (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) {\n        my $max_pve_version = PVE::QemuServer::Machine::get_pve_version($machine_version);\n        die \"Installed qemu-server (max feature level for $machine_major.$machine_minor is\"\n            . \" pve$max_pve_version) is too old to run machine type '$machine_type', please upgrade\"\n            . \" node '$nodename'\\n\";\n    }\n\n    # if a specific +pve version is required for a feature, use $version_guard\n    # instead of min_version to allow machines to be run with the minimum\n    # required version\n    my $required_pve_version = 0;\n    my $version_guard = sub {\n        my ($major, $minor, $pve) = @_;\n        return 0 if !min_version($machine_version, $major, $minor, $pve);\n        my $max_pve = PVE::QemuServer::Machine::get_pve_version(\"$major.$minor\");\n        return 1 if min_version($machine_version, $major, $minor, $max_pve + 1);\n        $required_pve_version = $pve if $pve && $pve > $required_pve_version;\n        return 1;\n    };\n\n    if ($kvm && !defined kvm_version()) {\n        die \"KVM virtualisation configured, but not available. Either disable in VM configuration\"\n            . \" or enable in BIOS.\\n\";\n    }\n\n    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n    my $hotplug_features =\n        parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');\n    my $use_old_bios_files = undef;\n    ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);\n\n    my $cmd = [];\n    if ($conf->{affinity}) {\n        push @$cmd, '/usr/bin/taskset', '--cpu-list', '--all-tasks', $conf->{affinity};\n    }\n\n    push @$cmd, $kvm_binary;\n\n    push @$cmd, '-id', $vmid;\n\n    my $vmname = $conf->{name} || \"vm$vmid\";\n\n    push @$cmd, '-name', \"$vmname,debug-threads=on\";\n\n    push @$cmd, '-no-shutdown';\n\n    my $use_virtio = 0;\n\n    my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket(vm_qmp_peer($vmid));\n    push @$cmd, '-chardev', \"socket,id=qmp,path=$qmpsocket,server=on,wait=off\";\n    push @$cmd, '-mon', \"chardev=qmp,mode=control\";\n\n    if (min_version($machine_version, 2, 12)) {\n        # QEMU 9.2 introduced a new 'reconnect-ms' option while deprecating the 'reconnect' option\n        my $reconnect_param = \"reconnect=5\";\n        if (min_version($kvmver, 9, 2)) { # this depends on the binary version\n            $reconnect_param = \"reconnect-ms=5000\";\n        }\n        push @$cmd, '-chardev', \"socket,id=qmp-event,path=/var/run/qmeventd.sock,$reconnect_param\";\n        push @$cmd, '-mon', \"chardev=qmp-event,mode=control\";\n    }\n\n    push @$cmd, '-pidfile', PVE::QemuServer::Helpers::vm_pidfile_name($vmid);\n\n    push @$cmd, '-daemonize';\n\n    if ($conf->{smbios1}) {\n        my $smbios_conf = parse_smbios1($conf->{smbios1});\n        if ($smbios_conf->{base64}) {\n            # Do not pass base64 flag to qemu\n            delete $smbios_conf->{base64};\n            my $smbios_string = \"\";\n            foreach my $key (keys %$smbios_conf) {\n                my $value;\n                if ($key eq \"uuid\") {\n                    $value = $smbios_conf->{uuid};\n                } else {\n                    $value = decode_base64($smbios_conf->{$key});\n                }\n                # qemu accepts any binary data, only commas need escaping by double comma\n                $value =~ s/,/,,/g;\n                $smbios_string .= \",\" . $key . \"=\" . $value if $value;\n            }\n            push @$cmd, '-smbios', \"type=1\" . $smbios_string;\n        } else {\n            push @$cmd, '-smbios', \"type=1,$conf->{smbios1}\";\n        }\n    }\n\n    if ($conf->{bios} && $conf->{bios} eq 'ovmf') {\n        die \"OVMF (UEFI) BIOS is not supported on 32-bit CPU types\\n\"\n            if !$forcecpu && get_cpu_bitness($conf->{cpu}, $arch) == 32;\n\n        my $hw_info = {\n            'cvm-type' => get_cvm_type($conf),\n            arch => $arch,\n            'machine-version' => $machine_version,\n            q35 => $q35,\n        };\n        my ($ovmf_cmd, $ovmf_machine_flags) = PVE::QemuServer::OVMF::print_ovmf_commandline(\n            $conf, $storecfg, $vmid, $hw_info, $version_guard, $is_template,\n        );\n        push $cmd->@*, $ovmf_cmd->@*;\n        push $machineFlags->@*, $ovmf_machine_flags->@*;\n    }\n\n    if ($q35) { # tell QEMU to load q35 config early\n        # we use different pcie-port hardware for qemu >= 4.0 for passthrough\n        if (min_version($machine_version, 4, 0)) {\n            push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg';\n        } else {\n            push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';\n        }\n    }\n\n    if (defined(my $fixups = qemu_created_version_fixups($conf, $forcemachine, $kvmver))) {\n        push @$cmd, $fixups->@*;\n    }\n\n    if ($conf->{vmgenid}) {\n        push @$devices, '-device', 'vmgenid,guid=' . $conf->{vmgenid};\n    }\n\n    # add usb controllers\n    my @usbcontrollers =\n        PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_version);\n    push @$devices, @usbcontrollers if @usbcontrollers;\n\n    my ($vga, $qxlnum) = get_vga_properties($conf, $arch, $machine_version, $winversion);\n\n    # enable absolute mouse coordinates (needed by vnc)\n    my $tablet = $conf->{tablet};\n    if (!defined($tablet)) {\n        $tablet = $defaults->{tablet};\n        $tablet = 0 if $qxlnum; # disable for spice because it is not needed\n        $tablet = 0 if $vga->{type} =~ m/^serial\\d+$/; # disable if we use serial terminal (no vga card)\n    }\n\n    if ($tablet) {\n        push @$devices, '-device', print_tabletdevice_full($conf, $arch) if $tablet;\n        my $kbd = print_keyboarddevice_full($conf, $arch);\n        push @$devices, '-device', $kbd if defined($kbd);\n    }\n\n    my $bootorder = device_bootorder($conf);\n\n    # host pci device passthrough\n    my ($kvm_off, $gpu_passthrough, $legacy_igd, $pci_devices) =\n        PVE::QemuServer::PCI::print_hostpci_devices(\n            $vmid, $conf, $devices, $vga, $winversion, $bridges, $arch, $bootorder, $dry_run,\n        );\n\n    # usb devices\n    my $usb_dev_features = {};\n    $usb_dev_features->{spice_usb3} = 1 if min_version($machine_version, 4, 0);\n\n    my @usbdevices = PVE::QemuServer::USB::get_usb_devices(\n        $conf, $usb_dev_features, $bootorder, $machine_version,\n    );\n    push @$devices, @usbdevices if @usbdevices;\n\n    # serial devices\n    for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {\n        my $path = $conf->{\"serial$i\"} or next;\n        if ($path eq 'socket') {\n            my $socket = \"/var/run/qemu-server/${vmid}.serial$i\";\n            push @$devices, '-chardev', \"socket,id=serial$i,path=$socket,server=on,wait=off\";\n            # On aarch64, serial0 is the UART device. QEMU only allows\n            # connecting UART devices via the '-serial' command line, as\n            # the device has a fixed slot on the hardware...\n            if ($arch eq 'aarch64' && $i == 0) {\n                push @$devices, '-serial', \"chardev:serial$i\";\n            } else {\n                push @$devices, '-device', \"isa-serial,chardev=serial$i\";\n            }\n        } else {\n            die \"no such serial device\\n\" if !-c $path;\n            push @$devices, '-chardev', \"serial,id=serial$i,path=$path\";\n            push @$devices, '-device', \"isa-serial,chardev=serial$i\";\n        }\n    }\n\n    # parallel devices\n    for (my $i = 0; $i < $MAX_PARALLEL_PORTS; $i++) {\n        if (my $path = $conf->{\"parallel$i\"}) {\n            die \"no such parallel device\\n\" if !-c $path;\n            my $devtype = $path =~ m!^/dev/usb/lp! ? 'serial' : 'parallel';\n            push @$devices, '-chardev', \"$devtype,id=parallel$i,path=$path\";\n            push @$devices, '-device', \"isa-parallel,chardev=parallel$i\";\n        }\n    }\n\n    if (min_version($machine_version, 4, 0) && (my $audio = conf_has_audio($conf))) {\n        my $audiopciaddr = print_pci_addr(\"audio0\", $bridges, $arch);\n        my $audio_devs = audio_devs($audio, $audiopciaddr, $machine_version);\n        push @$devices, @$audio_devs;\n    }\n\n    # Add a TPM only if the VM is not a template,\n    # to support backing up template VMs even if the TPM disk is write-protected.\n    add_tpm_device($vmid, $devices, $conf) if !$is_template;\n\n    my $sockets = 1;\n    $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused\n    $sockets = $conf->{sockets} if $conf->{sockets};\n\n    my $cores = $conf->{cores} || 1;\n\n    my $maxcpus = $sockets * $cores;\n\n    my $vcpus = $conf->{vcpus} ? $conf->{vcpus} : $maxcpus;\n\n    my $allowed_vcpus = $cpuinfo->{cpus};\n\n    die \"MAX $allowed_vcpus vcpus allowed per VM on this node\\n\" if ($allowed_vcpus < $maxcpus);\n\n    if ($hotplug_features->{cpu} && min_version($machine_version, 2, 7)) {\n        push @$cmd, '-smp', \"1,sockets=$sockets,cores=$cores,maxcpus=$maxcpus\";\n        for (my $i = 2; $i <= $vcpus; $i++) {\n            my $cpustr = print_cpu_device($conf, $arch, $i);\n            push @$cmd, '-device', $cpustr;\n        }\n\n    } else {\n\n        push @$cmd, '-smp', \"$vcpus,sockets=$sockets,cores=$cores,maxcpus=$maxcpus\";\n    }\n    push @$cmd, '-nodefaults';\n\n    push @$cmd, '-boot',\n        \"menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg\";\n\n    push $machineFlags->@*, 'acpi=off' if defined($conf->{acpi}) && $conf->{acpi} == 0;\n\n    push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;\n\n    if ($vga->{type} && $vga->{type} !~ m/^serial\\d+$/ && $vga->{type} ne 'none') {\n        push @$devices, '-device',\n            print_vga_device($conf, $vga, $arch, $machine_version, undef, $qxlnum, $bridges);\n\n        push @$cmd, '-display', 'egl-headless,gl=core' if $vga->{type} eq 'virtio-gl'; # VIRGL\n\n        my $socket = PVE::QemuServer::Helpers::vnc_socket($vmid);\n        push @$cmd, '-vnc', \"unix:$socket,password=on\";\n    } else {\n        push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';\n        push @$cmd, '-nographic';\n    }\n\n    # For now, handles only specific parts, but the final goal is to cover everything.\n    my $cfg2cmd_opts = { forcemachine => $forcemachine };\n    my $cfg2cmd = PVE::QemuServer::Cfg2Cmd->new($conf, $defaults, $version_guard, $cfg2cmd_opts);\n    my $generated = $cfg2cmd->generate();\n    push $cmd->@*, '-global', $_ for ($generated->global_flags() // [])->@*;\n    push $machineFlags->@*, ($generated->machine_flags() // [])->@*;\n    push $rtcFlags->@*, ($generated->rtc_flags() // [])->@*;\n\n    if ($forcecpu) {\n        push @$cmd, '-cpu', $forcecpu;\n    } else {\n        push @$cmd,\n            get_cpu_options(\n                $conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough,\n            );\n    }\n\n    my $virtiofs_enabled = PVE::QemuServer::Virtiofs::virtiofs_enabled($conf);\n\n    PVE::QemuServer::Memory::config(\n        $conf,\n        $vmid,\n        $sockets,\n        $cores,\n        $hotplug_features->{memory},\n        $virtiofs_enabled,\n        $cmd,\n        $machineFlags,\n    );\n\n    push @$cmd, '-S' if $conf->{freeze};\n\n    push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});\n\n    my $guest_agent = parse_guest_agent($conf);\n\n    if ($guest_agent->{enabled}) {\n        my $qgasocket = PVE::QemuServer::Helpers::qmp_socket(\n            { name => \"VM $vmid\", id => $vmid, type => 'qga' });\n        push @$devices, '-chardev', \"socket,path=$qgasocket,server=on,wait=off,id=qga0\";\n\n        if (!$guest_agent->{type} || $guest_agent->{type} eq 'virtio') {\n            my $pciaddr = print_pci_addr(\"qga0\", $bridges, $arch);\n            push @$devices, '-device', \"virtio-serial,id=qga0$pciaddr\";\n            push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';\n        } elsif ($guest_agent->{type} eq 'isa') {\n            push @$devices, '-device', \"isa-serial,chardev=qga0\";\n        }\n    }\n\n    my $rng = $conf->{rng0} ? parse_rng($conf->{rng0}) : undef;\n    if ($rng && $version_guard->(4, 1, 2)) {\n        my $rng_object = print_rng_object_commandline('rng0', $rng);\n        my $rng_device = print_rng_device_commandline('rng0', $rng, $bridges, $arch);\n        push @$devices, '-object', $rng_object;\n        push @$devices, '-device', $rng_device;\n    }\n\n    my $spice_port;\n\n    assert_clipboard_config($vga);\n    my $is_spice = $qxlnum || $vga->{type} =~ /^virtio/;\n\n    if ($is_spice || ($vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc')) {\n        if ($qxlnum > 1) {\n            if ($winversion) {\n                for (my $i = 1; $i < $qxlnum; $i++) {\n                    push @$devices, '-device',\n                        print_vga_device(\n                            $conf, $vga, $arch, $machine_version, $i, $qxlnum, $bridges,\n                        );\n                }\n            } else {\n                # assume other OS works like Linux\n                my ($ram, $vram) = (\"134217728\", \"67108864\");\n                if ($vga->{memory}) {\n                    $ram = PVE::Tools::convert_size($qxlnum * 4 * $vga->{memory}, 'mb' => 'b');\n                    $vram = PVE::Tools::convert_size($qxlnum * 2 * $vga->{memory}, 'mb' => 'b');\n                }\n                push @$cmd, '-global', \"qxl-vga.ram_size=$ram\";\n                push @$cmd, '-global', \"qxl-vga.vram_size=$vram\";\n            }\n        }\n\n        my $pciaddr = print_pci_addr(\"spice\", $bridges, $arch);\n\n        push @$devices, '-device', \"virtio-serial,id=spice$pciaddr\";\n        if ($vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {\n            push @$devices, '-chardev', 'qemu-vdagent,id=vdagent,name=vdagent,clipboard=on';\n        } else {\n            push @$devices, '-chardev', 'spicevmc,id=vdagent,name=vdagent';\n        }\n        push @$devices, '-device', \"virtserialport,chardev=vdagent,name=com.redhat.spice.0\";\n\n        if ($is_spice) {\n            my $pfamily = PVE::Tools::get_host_address_family($nodename);\n            my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);\n            die \"failed to get an ip address of type $pfamily for 'localhost'\\n\" if !@nodeaddrs;\n\n            my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});\n            $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);\n\n            my $spice_enhancement_str = $conf->{spice_enhancements} // '';\n            my $spice_enhancement =\n                parse_property_string($spice_enhancements_fmt, $spice_enhancement_str);\n            if ($spice_enhancement->{foldersharing}) {\n                push @$devices, '-chardev',\n                    \"spiceport,id=foldershare,name=org.spice-space.webdav.0\";\n                push @$devices, '-device',\n                    \"virtserialport,chardev=foldershare,name=org.spice-space.webdav.0\";\n            }\n\n            my $spice_opts =\n                \"tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on\";\n            $spice_opts .= \",streaming-video=$spice_enhancement->{videostreaming}\"\n                if $spice_enhancement->{videostreaming};\n            push @$devices, '-spice', \"$spice_opts\";\n        }\n    }\n\n    # enable balloon by default, unless explicitly disabled\n    if (!defined($conf->{balloon}) || $conf->{balloon}) {\n        my $pciaddr = print_pci_addr(\"balloon0\", $bridges, $arch);\n        my $ballooncmd = \"virtio-balloon-pci,id=balloon0$pciaddr\";\n        $ballooncmd .= \",free-page-reporting=on\" if min_version($machine_version, 6, 2);\n        push @$devices, '-device', $ballooncmd;\n    }\n\n    if ($conf->{watchdog}) {\n        my $wdopts = parse_watchdog($conf->{watchdog});\n        my $pciaddr = print_pci_addr(\"watchdog\", $bridges, $arch);\n        my $watchdog = $wdopts->{model} || 'i6300esb';\n        push @$devices, '-device', \"$watchdog$pciaddr\";\n        push @$devices, '-watchdog-action', $wdopts->{action} if $wdopts->{action};\n    }\n\n    my $scsicontroller = {};\n    my $ahcicontroller = {};\n    my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : $defaults->{scsihw};\n\n    # Add iscsi initiator name if available\n    if (my $initiator = get_iscsi_initiator_name()) {\n        push @$devices, '-iscsi', \"initiator-name=$initiator\";\n    }\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            # ignore efidisk here, already added in bios/fw handling code above\n            return if $drive->{interface} eq 'efidisk';\n            # similar for TPM\n            return if $drive->{interface} eq 'tpmstate';\n\n            $use_virtio = 1 if $ds =~ m/^virtio/;\n\n            $drive->{bootindex} = $bootorder->{$ds} if $bootorder->{$ds};\n\n            if ($drive->{interface} eq 'virtio') {\n                push @$cmd, '-object', \"iothread,id=iothread-$ds\" if $drive->{iothread};\n            }\n\n            if ($drive->{interface} eq 'scsi') {\n\n                my ($maxdev, $controller, $controller_prefix) =\n                    scsihw_infos($conf->{scsihw}, $drive->{index});\n\n                die\n                    \"scsi$drive->{index}: machine version 4.1~pve2 or higher is required to use more than 14 SCSI disks\\n\"\n                    if $drive->{index} > 13 && !&$version_guard(4, 1, 2);\n\n                my $pciaddr = print_pci_addr(\"$controller_prefix$controller\", $bridges, $arch);\n                my $scsihw_type =\n                    $scsihw =~ m/^virtio-scsi-single/ ? \"virtio-scsi-pci\" : $scsihw;\n\n                my $iothread = '';\n                if (\n                    $conf->{scsihw}\n                    && $conf->{scsihw} eq \"virtio-scsi-single\"\n                    && $drive->{iothread}\n                ) {\n                    $iothread .= \",iothread=iothread-$controller_prefix$controller\";\n                    push @$cmd, '-object', \"iothread,id=iothread-$controller_prefix$controller\";\n                } elsif ($drive->{iothread}) {\n                    log_warn(\n                        \"iothread is only valid with virtio disk or virtio-scsi-single controller, ignoring\\n\"\n                    );\n                }\n\n                my $queues = '';\n                if (\n                    $conf->{scsihw}\n                    && $conf->{scsihw} eq \"virtio-scsi-single\"\n                    && $drive->{queues}\n                ) {\n                    $queues = \",num_queues=$drive->{queues}\";\n                }\n\n                push @$devices, '-device',\n                    \"$scsihw_type,id=$controller_prefix$controller$pciaddr$iothread$queues\"\n                    if !$scsicontroller->{$controller};\n                $scsicontroller->{$controller} = 1;\n            }\n\n            if ($drive->{interface} eq 'sata') {\n                my $controller = int($drive->{index} / $PVE::QemuServer::Drive::MAX_SATA_DISKS);\n                my $pciaddr = print_pci_addr(\"ahci$controller\", $bridges, $arch);\n                push @$devices, '-device', \"ahci,id=ahci$controller,multifunction=on$pciaddr\"\n                    if !$ahcicontroller->{$controller};\n                $ahcicontroller->{$controller} = 1;\n            }\n\n            my $live_restore = $live_restore_backing->{$ds};\n\n            if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev\n                if ($drive->{file} ne 'none') {\n                    my $throttle_group =\n                        PVE::QemuServer::Blockdev::generate_throttle_group($drive);\n                    push @$cmd, '-object', to_json($throttle_group, { canonical => 1 });\n\n                    my $extra_blockdev_options = {};\n                    $extra_blockdev_options->{'live-restore'} = $live_restore if $live_restore;\n                    $extra_blockdev_options->{'read-only'} = 1 if $is_template;\n\n                    my $blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(\n                        $storecfg, $drive, $machine_version, $extra_blockdev_options,\n                    );\n                    push @$devices, '-blockdev', to_json($blockdev, { canonical => 1 });\n                }\n            } else {\n                my $live_blockdev_name = undef;\n                if ($live_restore) {\n                    $live_blockdev_name = $live_restore->{name};\n                    push @$devices, '-blockdev', $live_restore->{blockdev};\n                }\n\n                my $drive_cmd =\n                    print_drive_commandline_full($storecfg, $vmid, $drive, $live_blockdev_name);\n\n                if ($is_template) {\n                    # TODO PVE 10.x - since the temporary config for starting templates for backup\n                    # uses the latest machine version, this should already be dead code. It's kept\n                    # for now if for whatever reason an older QEMU build is used (e.g. bisecting).\n                    my $interface = $drive->{interface};\n                    $drive_cmd .= ',readonly=on' if $interface ne 'ide' && $interface ne 'sata';\n                }\n\n                push @$devices, '-drive', $drive_cmd;\n            }\n\n            push @$devices, '-device',\n                print_drivedevice_full(\n                    $storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type,\n                );\n        },\n    );\n\n    my $nets_host_mtu =\n        { map { split('=', $_) } PVE::Tools::split_list($options->{'nets-host-mtu'}) };\n    for (my $i = 0; $i < $MAX_NETS; $i++) {\n        my $netname = \"net$i\";\n\n        next if !$conf->{$netname};\n        my $d = PVE::QemuServer::Network::parse_net($conf->{$netname});\n        next if !$d;\n        # save the MAC addr here (could be auto-gen. in some odd setups) for FDB registering later?\n\n        $use_virtio = 1 if $d->{model} eq 'virtio';\n\n        $d->{bootindex} = $bootorder->{$netname} if $bootorder->{$netname};\n\n        my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, $netname);\n        push @$devices, '-netdev', $netdevfull;\n\n        # force +pve1 if machine version 10.0, for host_mtu differentiation\n        $version_guard->(10, 0, 1);\n        my $netdevicefull = print_netdevice_full(\n            $vmid,\n            $conf,\n            $d,\n            $netname,\n            $bridges,\n            $use_old_bios_files,\n            $arch,\n            $machine_version,\n            $nets_host_mtu->{$netname},\n        );\n\n        push @$devices, '-device', $netdevicefull;\n    }\n\n    if ($conf->{ivshmem}) {\n        my $ivshmem = parse_property_string($ivshmem_fmt, $conf->{ivshmem});\n\n        my $bus;\n        if ($q35) {\n            $bus = print_pcie_addr(\"ivshmem\");\n        } else {\n            $bus = print_pci_addr(\"ivshmem\", $bridges, $arch);\n        }\n\n        my $ivshmem_name = $ivshmem->{name} // $vmid;\n        my $path = '/dev/shm/pve-shm-' . $ivshmem_name;\n\n        push @$devices, '-device', \"ivshmem-plain,memdev=ivshmem$bus,\";\n        push @$devices, '-object',\n            \"memory-backend-file,id=ivshmem,share=on,mem-path=$path\" . \",size=$ivshmem->{size}M\";\n    }\n\n    # pci.4 is nested in pci.1\n    $bridges->{1} = 1 if $bridges->{4};\n\n    if (!$q35) { # add pci bridges\n        if (min_version($machine_version, 2, 3)) {\n            $bridges->{1} = 1;\n            $bridges->{2} = 1;\n        }\n        $bridges->{3} = 1 if $scsihw =~ m/^virtio-scsi-single/;\n    }\n\n    for my $k (sort { $b cmp $a } keys %$bridges) {\n        next if $q35 && $k < 4; # q35.cfg already includes bridges up to 3\n\n        my $k_name = $k;\n        if ($k == 2 && $legacy_igd) {\n            $k_name = \"$k-igd\";\n        }\n        my $pciaddr = print_pci_addr(\"pci.$k_name\", undef, $arch);\n        my $devstr = \"pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr\";\n\n        if ($q35) { # add after -readconfig pve-q35.cfg\n            splice @$devices, 2, 0, '-device', $devstr;\n        } else {\n            unshift @$devices, '-device', $devstr if $k > 0;\n        }\n    }\n\n    if (!$kvm) {\n        push @$machineFlags, 'accel=tcg';\n    }\n    my $power_state_flags =\n        PVE::QemuServer::Machine::get_power_state_flags($machine_conf, $arch, $version_guard);\n    push $cmd->@*, $power_state_flags->@* if defined($power_state_flags);\n\n    push @$machineFlags, 'smm=off' if should_disable_smm($conf, $vga, $machine_type);\n\n    my $machine_type_min = $machine_type;\n    $machine_type_min =~ s/\\+pve\\d+$//;\n    $machine_type_min .= \"+pve$required_pve_version\";\n    push @$machineFlags, \"type=${machine_type_min}\";\n\n    PVE::QemuServer::Machine::assert_valid_machine_property($machine_conf);\n\n    if (my $viommu = $machine_conf->{viommu}) {\n        my $viommu_devstr = '';\n        if ($machine_conf->{'aw-bits'}) {\n            $viommu_devstr .= \",aw-bits=$machine_conf->{'aw-bits'}\";\n\n            # TODO remove message once this gets properly checked/warned about in QEMU itself.\n            print \"vIOMMU 'aw-bits' set to $machine_conf->{'aw-bits'}. Sometimes it is necessary to\"\n                . \" set the CPU's 'guest-phys-bits' to the same value.\\n\";\n        }\n\n        if ($viommu eq 'intel') {\n            $viommu_devstr = \"intel-iommu,intremap=on,caching-mode=on$viommu_devstr\";\n            unshift @$devices, '-device', $viommu_devstr;\n            push @$machineFlags, 'kernel-irqchip=split';\n        } elsif ($viommu eq 'virtio') {\n            $viommu_devstr = \"virtio-iommu-pci$viommu_devstr\";\n            push @$devices, '-device', $viommu_devstr;\n        }\n    }\n\n    if ($conf->{'amd-sev'}) {\n        push @$devices, '-object', get_amd_sev_object($conf->{'amd-sev'}, $conf->{bios});\n        push @$machineFlags, 'confidential-guest-support=sev0';\n    } elsif ($conf->{'intel-tdx'}) {\n        my $tdx_object = get_intel_tdx_object($conf->{'intel-tdx'}, $conf->{bios});\n        push @$devices, '-object', to_json($tdx_object, { canonical => 1 });\n        push @$machineFlags, 'confidential-guest-support=tdx0';\n        push @$machineFlags, 'kernel_irqchip=split';\n    }\n\n    push @$machineFlags, 'mem-merge=off' if defined($conf->{'allow-ksm'}) && !$conf->{'allow-ksm'};\n\n    PVE::QemuServer::Virtiofs::config($conf, $vmid, $devices);\n\n    push @$cmd, @$devices;\n    push @$cmd, '-rtc', join(',', @$rtcFlags) if scalar(@$rtcFlags);\n    push @$cmd, '-machine', join(',', @$machineFlags) if scalar(@$machineFlags);\n\n    if (my $vmstate = $conf->{vmstate}) {\n        my $statepath = PVE::Storage::path($storecfg, $vmstate);\n        push @$cmd, '-loadstate', $statepath;\n        print \"activating and using '$vmstate' as vmstate\\n\";\n    }\n\n    if ($is_template) {\n        # TODO PVE 10.x - since the temporary config for starting templates for backup uses the\n        # latest machine version, this should already be dead code. It's kept for now if for\n        # whatever reason an older QEMU build is used (e.g. bisecting).\n\n        # needed to workaround base volumes being read-only\n        push @$cmd, '-snapshot';\n    }\n\n    # add custom args\n    if ($conf->{args}) {\n        my $aa = PVE::Tools::split_args($conf->{args});\n        push @$cmd, @$aa;\n    }\n\n    return wantarray ? ($cmd, $spice_port, $pci_devices, $conf) : $cmd;\n}\n\nsub spice_port {\n    my ($vmid) = @_;\n\n    my $res = mon_cmd($vmid, 'query-spice');\n\n    return $res->{'tls-port'} || $res->{'port'} || die \"no spice port\\n\";\n}\n\nsub vm_devices_list {\n    my ($vmid) = @_;\n\n    my $res = mon_cmd($vmid, 'query-pci');\n    my $devices_to_check = [];\n    my $devices = {};\n    foreach my $pcibus (@$res) {\n        push @$devices_to_check, @{ $pcibus->{devices} },;\n    }\n\n    while (@$devices_to_check) {\n        my $to_check = [];\n        for my $d (@$devices_to_check) {\n            $devices->{ $d->{'qdev_id'} } = 1 if $d->{'qdev_id'};\n            next if !$d->{'pci_bridge'} || !$d->{'pci_bridge'}->{devices};\n\n            $devices->{ $d->{'qdev_id'} } += scalar(@{ $d->{'pci_bridge'}->{devices} });\n            push @$to_check, @{ $d->{'pci_bridge'}->{devices} };\n        }\n        $devices_to_check = $to_check;\n    }\n\n    # Block device IDs need to be checked at the qdev level, since with '-blockdev', the 'device'\n    # property will not be set.\n    my $resblock = mon_cmd($vmid, 'query-block');\n    for my $block ($resblock->@*) {\n        my $qdev_id = $block->{qdev} or next;\n        my $drive_id = PVE::QemuServer::Blockdev::qdev_id_to_drive_id($qdev_id);\n        $devices->{$drive_id} = 1;\n    }\n\n    my $resmice = mon_cmd($vmid, 'query-mice');\n    foreach my $mice (@$resmice) {\n        if ($mice->{name} eq 'QEMU HID Tablet') {\n            $devices->{tablet} = 1;\n            last;\n        }\n    }\n\n    # for usb devices there is no query-usb\n    # but we can iterate over the entries in\n    # qom-list path=/machine/peripheral\n    my $resperipheral = mon_cmd($vmid, 'qom-list', path => '/machine/peripheral');\n    foreach my $per (@$resperipheral) {\n        if ($per->{name} =~ m/^usb(?:redirdev)?\\d+$/) {\n            $devices->{ $per->{name} } = 1;\n        }\n    }\n\n    return $devices;\n}\n\nsub vm_deviceplug {\n    my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;\n\n    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n\n    my $devices_list = vm_devices_list($vmid);\n    return 1 if defined($devices_list->{$deviceid});\n\n    # add PCI bridge if we need it for the device\n    qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid, $arch, $machine_type);\n\n    if ($deviceid eq 'tablet') {\n        qemu_deviceadd($vmid, print_tabletdevice_full($conf, $arch));\n    } elsif ($deviceid eq 'keyboard') {\n        qemu_deviceadd($vmid, print_keyboarddevice_full($conf, $arch));\n    } elsif ($deviceid =~ m/^usbredirdev(\\d+)$/) {\n        my $id = $1;\n        qemu_spice_usbredir_chardev_add($vmid, \"usbredirchardev$id\");\n        qemu_deviceadd($vmid, PVE::QemuServer::USB::print_spice_usbdevice($id, \"xhci\", $id + 1));\n    } elsif ($deviceid =~ m/^usb(\\d+)$/) {\n        qemu_deviceadd(\n            $vmid,\n            PVE::QemuServer::USB::print_usbdevice_full($conf, $deviceid, $device, {}, $1 + 1),\n        );\n    } elsif ($deviceid =~ m/^(virtio)(\\d+)$/) {\n        qemu_iothread_add($vmid, $deviceid, $device);\n\n        qemu_driveadd($storecfg, $vmid, $device);\n        my $devicefull =\n            print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);\n\n        qemu_deviceadd($vmid, $devicefull);\n        eval { qemu_deviceaddverify($vmid, $deviceid); };\n        if (my $err = $@) {\n            eval { qemu_drivedel($vmid, $deviceid); };\n            warn $@ if $@;\n            die $err;\n        }\n    } elsif ($deviceid =~ m/^(virtioscsi|scsihw)(\\d+)$/) {\n        my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : \"lsi\";\n        my $pciaddr = print_pci_addr($deviceid, undef, $arch);\n        my $scsihw_type = $scsihw eq 'virtio-scsi-single' ? \"virtio-scsi-pci\" : $scsihw;\n\n        my $devicefull = \"$scsihw_type,id=$deviceid$pciaddr\";\n\n        if ($deviceid =~ m/^virtioscsi(\\d+)$/ && $device->{iothread}) {\n            qemu_iothread_add($vmid, $deviceid, $device);\n            $devicefull .= \",iothread=iothread-$deviceid\";\n        }\n\n        if ($deviceid =~ m/^virtioscsi(\\d+)$/ && $device->{queues}) {\n            $devicefull .= \",num_queues=$device->{queues}\";\n        }\n\n        qemu_deviceadd($vmid, $devicefull);\n        qemu_deviceaddverify($vmid, $deviceid);\n    } elsif ($deviceid =~ m/^(scsi)(\\d+)$/) {\n        qemu_findorcreatescsihw($storecfg, $conf, $vmid, $device, $arch, $machine_type);\n        qemu_driveadd($storecfg, $vmid, $device);\n\n        my $devicefull =\n            print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);\n        eval { qemu_deviceadd($vmid, $devicefull); };\n        if (my $err = $@) {\n            eval { qemu_drivedel($vmid, $deviceid); };\n            warn $@ if $@;\n            die $err;\n        }\n    } elsif ($deviceid =~ m/^(net)(\\d+)$/) {\n        return if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);\n\n        my $machine_type = PVE::QemuServer::Machine::qemu_machine_pxe($vmid, $conf);\n        my $machine_version = PVE::QemuServer::Machine::extract_version($machine_type);\n        my $use_old_bios_files = undef;\n        ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);\n\n        my $netdevicefull = print_netdevice_full(\n            $vmid,\n            $conf,\n            $device,\n            $deviceid,\n            undef,\n            $use_old_bios_files,\n            $arch,\n            $machine_version,\n        );\n        qemu_deviceadd($vmid, $netdevicefull);\n        eval {\n            qemu_deviceaddverify($vmid, $deviceid);\n            qemu_set_link_status($vmid, $deviceid, !$device->{link_down});\n        };\n        if (my $err = $@) {\n            eval { qemu_netdevdel($vmid, $deviceid); };\n            warn $@ if $@;\n            die $err;\n        }\n    } elsif (!$q35 && $deviceid =~ m/^(pci\\.)(\\d+)$/) {\n        my $bridgeid = $2;\n        my $pciaddr = print_pci_addr($deviceid, undef, $arch);\n        my $devicefull = \"pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr\";\n\n        qemu_deviceadd($vmid, $devicefull);\n        qemu_deviceaddverify($vmid, $deviceid);\n    } else {\n        die \"can't hotplug device '$deviceid'\\n\";\n    }\n\n    return 1;\n}\n\n# fixme: this should raise exceptions on error!\nsub vm_deviceunplug {\n    my ($vmid, $conf, $deviceid) = @_;\n\n    my $devices_list = vm_devices_list($vmid);\n    return 1 if !defined($devices_list->{$deviceid});\n\n    my $bootdisks = PVE::QemuServer::Drive::get_bootdisks($conf);\n    die \"can't unplug bootdisk '$deviceid'\\n\" if grep { $_ eq $deviceid } @$bootdisks;\n\n    if ($deviceid eq 'tablet' || $deviceid eq 'keyboard' || $deviceid eq 'xhci') {\n        qemu_devicedel($vmid, $deviceid);\n    } elsif ($deviceid =~ m/^usbredirdev\\d+$/) {\n        qemu_devicedel($vmid, $deviceid);\n        qemu_devicedelverify($vmid, $deviceid);\n    } elsif ($deviceid =~ m/^usb\\d+$/) {\n        qemu_devicedel($vmid, $deviceid);\n        qemu_devicedelverify($vmid, $deviceid);\n    } elsif ($deviceid =~ m/^(virtio)(\\d+)$/) {\n        my $device = parse_drive($deviceid, $conf->{$deviceid});\n\n        qemu_devicedel($vmid, $deviceid);\n        qemu_devicedelverify($vmid, $deviceid);\n        qemu_drivedel($vmid, $deviceid);\n        qemu_iothread_del($vmid, $deviceid, $device);\n    } elsif ($deviceid =~ m/^(virtioscsi|scsihw)(\\d+)$/) {\n        qemu_devicedel($vmid, $deviceid);\n        qemu_devicedelverify($vmid, $deviceid, 15);\n    } elsif ($deviceid =~ m/^(scsi)(\\d+)$/) {\n        my $device = parse_drive($deviceid, $conf->{$deviceid});\n\n        qemu_devicedel($vmid, $deviceid);\n        qemu_devicedelverify($vmid, $deviceid);\n        qemu_drivedel($vmid, $deviceid);\n        qemu_deletescsihw($conf, $vmid, $deviceid);\n\n        qemu_iothread_del($vmid, \"virtioscsi$device->{index}\", $device)\n            if $conf->{scsihw} && ($conf->{scsihw} eq 'virtio-scsi-single');\n    } elsif ($deviceid =~ m/^(net)(\\d+)$/) {\n        qemu_devicedel($vmid, $deviceid);\n        qemu_devicedelverify($vmid, $deviceid);\n        qemu_netdevdel($vmid, $deviceid);\n    } else {\n        die \"can't unplug device '$deviceid'\\n\";\n    }\n\n    return 1;\n}\n\nsub qemu_spice_usbredir_chardev_add {\n    my ($vmid, $id) = @_;\n\n    mon_cmd(\n        $vmid,\n        \"chardev-add\",\n        (\n            id => $id,\n            backend => {\n                type => 'spicevmc',\n                data => {\n                    type => \"usbredir\",\n                },\n            },\n        ),\n    );\n}\n\nsub qemu_iothread_add {\n    my ($vmid, $deviceid, $device) = @_;\n\n    if ($device->{iothread}) {\n        my $iothreads = vm_iothreads_list($vmid);\n        qemu_objectadd($vmid, \"iothread-$deviceid\", \"iothread\")\n            if !$iothreads->{\"iothread-$deviceid\"};\n    }\n}\n\nsub qemu_iothread_del {\n    my ($vmid, $deviceid, $device) = @_;\n\n    if ($device->{iothread}) {\n        my $iothreads = vm_iothreads_list($vmid);\n        qemu_objectdel($vmid, \"iothread-$deviceid\") if $iothreads->{\"iothread-$deviceid\"};\n    }\n}\n\nsub qemu_driveadd {\n    my ($storecfg, $vmid, $device) = @_;\n\n    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n\n    # for the switch to -blockdev\n    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {\n        PVE::QemuServer::Blockdev::attach($storecfg, $vmid, $device, {});\n        return 1;\n    } else {\n        my $drive = print_drive_commandline_full($storecfg, $vmid, $device, undef);\n        $drive =~ s/\\\\/\\\\\\\\/g;\n        my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, \"drive_add auto \\\"$drive\\\"\", 60);\n\n        # If the command succeeds qemu prints: \"OK\"\n        return 1 if $ret =~ m/OK/s;\n\n        die \"adding drive failed: $ret\\n\";\n    }\n}\n\nsub qemu_drivedel {\n    my ($vmid, $deviceid) = @_;\n\n    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n\n    # for the switch to -blockdev\n    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {\n        PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), \"drive-$deviceid\");\n        return 1;\n    } else {\n        my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, \"drive_del drive-$deviceid\", 10 * 60);\n        $ret =~ s/^\\s+//;\n\n        return 1 if $ret eq \"\";\n\n        # NB: device not found errors mean the drive was auto-deleted and we ignore the error\n        return 1 if $ret =~ m/Device \\'.*?\\' not found/s;\n\n        die \"deleting drive $deviceid failed : $ret\\n\";\n    }\n}\n\nsub qemu_deviceaddverify {\n    my ($vmid, $deviceid) = @_;\n\n    for (my $i = 0; $i <= 5; $i++) {\n        my $devices_list = vm_devices_list($vmid);\n        return 1 if defined($devices_list->{$deviceid});\n        sleep 1;\n    }\n\n    die \"error on hotplug device '$deviceid'\\n\";\n}\n\nsub qemu_devicedelverify {\n    my ($vmid, $deviceid, $timeout) = @_;\n\n    $timeout //= 5;\n\n    # need to verify that the device is correctly removed as device_del\n    # is async and empty return is not reliable\n\n    for (my $i = 0; $i <= $timeout; $i++) {\n        my $devices_list = vm_devices_list($vmid);\n        return 1 if !defined($devices_list->{$deviceid});\n        sleep 1;\n    }\n\n    die \"error on hot-unplugging device '$deviceid' - still busy in guest?\\n\";\n}\n\nsub qemu_findorcreatescsihw {\n    my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;\n\n    my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf->{scsihw}, $device->{index});\n\n    my $scsihwid = \"$controller_prefix$controller\";\n    my $devices_list = vm_devices_list($vmid);\n\n    if (!defined($devices_list->{$scsihwid})) {\n        vm_deviceplug($storecfg, $conf, $vmid, $scsihwid, $device, $arch, $machine_type);\n    }\n\n    return 1;\n}\n\nsub qemu_deletescsihw {\n    my ($conf, $vmid, $opt) = @_;\n\n    my $device = parse_drive($opt, $conf->{$opt});\n\n    if ($conf->{scsihw} && ($conf->{scsihw} eq 'virtio-scsi-single')) {\n        vm_deviceunplug($vmid, $conf, \"virtioscsi$device->{index}\");\n        return 1;\n    }\n\n    my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf->{scsihw}, $device->{index});\n\n    my $devices_list = vm_devices_list($vmid);\n    foreach my $opt (keys %{$devices_list}) {\n        if (is_valid_drivename($opt)) {\n            my $drive = parse_drive($opt, $conf->{$opt});\n            if (\n                $drive->{interface} eq 'scsi'\n                && $drive->{index} < (($maxdev - 1) * ($controller + 1))\n            ) {\n                return 1;\n            }\n        }\n    }\n\n    my $scsihwid = \"scsihw$controller\";\n\n    vm_deviceunplug($vmid, $conf, $scsihwid);\n\n    return 1;\n}\n\nsub qemu_add_pci_bridge {\n    my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;\n\n    my $bridges = {};\n\n    my $bridgeid;\n\n    print_pci_addr($device, $bridges, $arch);\n\n    while (my ($k, $v) = each %$bridges) {\n        $bridgeid = $k;\n    }\n    return 1 if !defined($bridgeid) || $bridgeid < 1;\n\n    my $bridge = \"pci.$bridgeid\";\n    my $devices_list = vm_devices_list($vmid);\n\n    if (!defined($devices_list->{$bridge})) {\n        vm_deviceplug($storecfg, $conf, $vmid, $bridge, $arch, $machine_type);\n    }\n\n    return 1;\n}\n\nsub qemu_set_link_status {\n    my ($vmid, $device, $up) = @_;\n\n    mon_cmd(\n        $vmid, \"set_link\",\n        name => $device,\n        up => $up ? JSON::true : JSON::false,\n    );\n}\n\nsub qemu_netdevadd {\n    my ($vmid, $conf, $arch, $device, $deviceid) = @_;\n\n    my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);\n    my %options = split(/[=,]/, $netdev);\n\n    if (defined(my $vhost = $options{vhost})) {\n        $options{vhost} = JSON::boolean(PVE::JSONSchema::parse_boolean($vhost));\n    }\n\n    if (defined(my $queues = $options{queues})) {\n        $options{queues} = $queues + 0;\n    }\n\n    mon_cmd($vmid, \"netdev_add\", %options);\n    return 1;\n}\n\nsub qemu_netdevdel {\n    my ($vmid, $deviceid) = @_;\n\n    mon_cmd($vmid, \"netdev_del\", id => $deviceid);\n}\n\nsub qemu_usb_hotplug {\n    my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;\n\n    return if !$device;\n\n    # remove the old one first\n    vm_deviceunplug($vmid, $conf, $deviceid);\n\n    # check if xhci controller is necessary and available\n    my $added_xhci;\n    my $devicelist = vm_devices_list($vmid);\n\n    if (!$devicelist->{xhci}) {\n        my $pciaddr = print_pci_addr(\"xhci\", undef, $arch);\n        qemu_deviceadd($vmid, PVE::QemuServer::USB::print_qemu_xhci_controller($pciaddr));\n        $added_xhci = 1;\n    }\n\n    # add the new one\n    eval { vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type); };\n    if (my $err = $@) {\n        if ($added_xhci) {\n            eval { vm_deviceunplug($vmid, $conf, 'xhci'); };\n            warn \"failed to unplug xhci controller - $@\" if $@;\n        }\n        die $err;\n    }\n\n    return;\n}\n\nsub qemu_cpu_hotplug {\n    my ($vmid, $conf, $vcpus) = @_;\n\n    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n\n    my $sockets = 1;\n    $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused\n    $sockets = $conf->{sockets} if $conf->{sockets};\n    my $cores = $conf->{cores} || 1;\n    my $maxcpus = $sockets * $cores;\n\n    $vcpus = $maxcpus if !$vcpus;\n\n    die \"you can't add more vcpus than maxcpus\\n\"\n        if $vcpus > $maxcpus;\n\n    my $currentvcpus = $conf->{vcpus} || $maxcpus;\n\n    if ($vcpus < $currentvcpus) {\n\n        if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 2, 7)) {\n\n            for (my $i = $currentvcpus; $i > $vcpus; $i--) {\n                qemu_devicedel($vmid, \"cpu$i\");\n                my $retry = 0;\n                my $currentrunningvcpus = undef;\n                while (1) {\n                    $currentrunningvcpus = mon_cmd($vmid, \"query-cpus-fast\");\n                    last if scalar(@{$currentrunningvcpus}) == $i - 1;\n                    raise_param_exc({ vcpus => \"error unplugging cpu$i\" }) if $retry > 5;\n                    $retry++;\n                    sleep 1;\n                }\n                #update conf after each successful cpu unplug\n                $conf->{vcpus} = scalar(@{$currentrunningvcpus});\n                PVE::QemuConfig->write_config($vmid, $conf);\n            }\n        } else {\n            die \"cpu hot-unplugging requires qemu version 2.7 or higher\\n\";\n        }\n\n        return;\n    }\n\n    my $currentrunningvcpus = mon_cmd($vmid, \"query-cpus-fast\");\n    die \"vcpus in running vm does not match its configuration\\n\"\n        if scalar(@{$currentrunningvcpus}) != $currentvcpus;\n\n    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 2, 7)) {\n        my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n\n        for (my $i = $currentvcpus + 1; $i <= $vcpus; $i++) {\n            my $cpustr = print_cpu_device($conf, $arch, $i);\n            qemu_deviceadd($vmid, $cpustr);\n\n            my $retry = 0;\n            my $currentrunningvcpus = undef;\n            while (1) {\n                $currentrunningvcpus = mon_cmd($vmid, \"query-cpus-fast\");\n                last if scalar(@{$currentrunningvcpus}) == $i;\n                raise_param_exc({ vcpus => \"error hotplugging cpu$i\" }) if $retry > 10;\n                sleep 1;\n                $retry++;\n            }\n            #update conf after each successful cpu hotplug\n            $conf->{vcpus} = scalar(@{$currentrunningvcpus});\n            PVE::QemuConfig->write_config($vmid, $conf);\n        }\n    } else {\n\n        for (my $i = $currentvcpus; $i < $vcpus; $i++) {\n            mon_cmd($vmid, \"cpu-add\", id => int($i));\n        }\n    }\n}\n\nsub qemu_volume_snapshot {\n    my ($vmid, $deviceid, $storecfg, $drive, $snap) = @_;\n\n    my $volid = $drive->{file};\n    my $running = check_running($vmid);\n\n    my $do_snapshots_type = do_snapshots_type($storecfg, $volid, $deviceid, $running);\n\n    if ($do_snapshots_type eq 'internal') {\n        print \"internal qemu snapshot\\n\";\n        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);\n        qmp_cmd($qmp_peer, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);\n    } elsif ($do_snapshots_type eq 'external') {\n        my $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n        if (!PVE::QemuServer::Machine::is_machine_version_at_least($machine_version, 10, 0)) {\n            die \"storage for '$volid' is configured for snapshots as a volume chain - this requires\"\n                . \" QEMU machine version >= 10.0. See\"\n                . \" https://pve.proxmox.com/wiki/QEMU_Machine_Version_Upgrade\\n\";\n        }\n        my $storeid = (PVE::Storage::parse_volume_id($volid))[0];\n        my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n        print \"external qemu snapshot\\n\";\n        my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);\n        my $parent_snap = $snapshots->{'current'}->{parent};\n        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);\n        PVE::QemuServer::VolumeChain::blockdev_external_snapshot(\n            $storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $snap, $parent_snap,\n        );\n    } elsif ($do_snapshots_type eq 'storage') {\n        PVE::Storage::volume_snapshot($storecfg, $volid, $snap);\n    }\n}\n\nsub qemu_volume_snapshot_delete {\n    my ($vmid, $storecfg, $drive, $snap) = @_;\n\n    my $volid = $drive->{file};\n    my $running = check_running($vmid);\n    my $attached_deviceid;\n\n    if ($running) {\n        my $conf = PVE::QemuConfig->load_config($vmid);\n        PVE::QemuConfig->foreach_volume(\n            $conf,\n            sub {\n                my ($ds, $drive) = @_;\n                $attached_deviceid = \"drive-$ds\" if $drive->{file} eq $volid;\n            },\n        );\n    }\n\n    my $do_snapshots_type = do_snapshots_type($storecfg, $volid, $attached_deviceid, $running);\n\n    if ($do_snapshots_type eq 'internal') {\n        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);\n        qmp_cmd(\n            $qmp_peer,\n            'blockdev-snapshot-delete-internal-sync',\n            device => $attached_deviceid,\n            name => $snap,\n        );\n    } elsif ($do_snapshots_type eq 'external') {\n        my $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n        if (!PVE::QemuServer::Machine::is_machine_version_at_least($machine_version, 10, 0)) {\n            die \"storage for '$volid' is configured for snapshots as a volume chain - this requires\"\n                . \" QEMU machine version >= 10.0. See\"\n                . \" https://pve.proxmox.com/wiki/QEMU_Machine_Version_Upgrade\\n\";\n        }\n\n        print \"delete qemu external snapshot\\n\";\n\n        my $path = PVE::Storage::path($storecfg, $volid);\n        my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);\n\n        die \"could not find snapshot '$snap' for volume '$volid'\\n\"\n            if !defined($snapshots->{$snap});\n\n        my $parentsnap = $snapshots->{$snap}->{parent};\n        my $childsnap = $snapshots->{$snap}->{child};\n\n        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);\n\n        # if we delete the first snasphot, we commit because the first snapshot original base image, it should be big.\n        # improve-me: if firstsnap > child : commit, if firstsnap < child do a stream.\n        if (!$parentsnap) {\n            print \"delete first snapshot $snap\\n\";\n            PVE::QemuServer::VolumeChain::blockdev_commit(\n                $storecfg,\n                $qmp_peer,\n                $machine_version,\n                $attached_deviceid,\n                $drive,\n                $childsnap,\n                $snap,\n            );\n\n            PVE::Storage::rename_snapshot($storecfg, $volid, $snap, $childsnap);\n\n            PVE::QemuServer::VolumeChain::blockdev_replace(\n                $storecfg,\n                $qmp_peer,\n                $machine_version,\n                $attached_deviceid,\n                $drive,\n                $snap,\n                $childsnap,\n                $snapshots->{$childsnap}->{child},\n            );\n        } else {\n            #intermediate snapshot, we always stream the snapshot to child snapshot\n            print \"stream intermediate snapshot $snap to $childsnap\\n\";\n            PVE::QemuServer::VolumeChain::blockdev_stream(\n                $storecfg,\n                $qmp_peer,\n                $machine_version,\n                $attached_deviceid,\n                $drive,\n                $snap,\n                $parentsnap,\n                $childsnap,\n            );\n        }\n    } elsif ($do_snapshots_type eq 'storage') {\n        PVE::Storage::volume_snapshot_delete(\n            $storecfg,\n            $volid,\n            $snap,\n            $attached_deviceid ? 1 : undef,\n        );\n    }\n}\n\nsub foreach_volid {\n    my ($conf, $func, @param) = @_;\n\n    my $volhash = {};\n\n    my $test_volid = sub {\n        my ($key, $drive, $snapname, $pending) = @_;\n\n        my $volid = $drive->{file};\n        return if !$volid;\n\n        $volhash->{$volid}->{cdrom} //= 1;\n        $volhash->{$volid}->{cdrom} = 0 if !drive_is_cdrom($drive);\n\n        $volhash->{$volid}->{is_cloudinit} //= 0;\n        $volhash->{$volid}->{is_cloudinit} = 1 if drive_is_cloudinit($drive);\n\n        my $replicate = $drive->{replicate} // 1;\n        $volhash->{$volid}->{replicate} //= 0;\n        $volhash->{$volid}->{replicate} = 1 if $replicate;\n\n        $volhash->{$volid}->{shared} //= 0;\n        $volhash->{$volid}->{shared} = 1 if $drive->{shared};\n\n        $volhash->{$volid}->{is_unused} //= 0;\n        $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\\d+$/;\n\n        $volhash->{$volid}->{is_attached} //= 0;\n        $volhash->{$volid}->{is_attached} = 1\n            if !$volhash->{$volid}->{is_unused} && !defined($snapname) && !$pending;\n\n        $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1\n            if defined($snapname);\n\n        $volhash->{$volid}->{referenced_in_pending} = 1 if $pending;\n\n        my $size = $drive->{size};\n        $volhash->{$volid}->{size} //= $size if $size;\n\n        $volhash->{$volid}->{is_vmstate} //= 0;\n        $volhash->{$volid}->{is_vmstate} = 1 if $key eq 'vmstate';\n\n        $volhash->{$volid}->{is_tpmstate} //= 0;\n        $volhash->{$volid}->{is_tpmstate} = 1 if $key eq 'tpmstate0';\n\n        $volhash->{$volid}->{drivename} = $key if is_valid_drivename($key);\n    };\n\n    my $include_opts = {\n        extra_keys => ['vmstate'],\n        include_unused => 1,\n    };\n\n    PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $test_volid);\n\n    PVE::QemuConfig->foreach_volume_full($conf->{pending}, $include_opts, $test_volid, undef, 1)\n        if defined($conf->{pending}) && $conf->{pending}->%*;\n\n    foreach my $snapname (keys %{ $conf->{snapshots} }) {\n        my $snap = $conf->{snapshots}->{$snapname};\n        PVE::QemuConfig->foreach_volume_full($snap, $include_opts, $test_volid, $snapname);\n    }\n\n    foreach my $volid (keys %$volhash) {\n        &$func($volid, $volhash->{$volid}, @param);\n    }\n}\n\nmy $fast_plug_option = {\n    'description' => 1,\n    'hookscript' => 1,\n    'lock' => 1,\n    'migrate_downtime' => 1,\n    'migrate_speed' => 1,\n    'name' => 1,\n    'onboot' => 1,\n    'protection' => 1,\n    'shares' => 1,\n    'startup' => 1,\n    'tags' => 1,\n    'vmstatestorage' => 1,\n};\n\nfor my $opt (keys %$confdesc_cloudinit) {\n    $fast_plug_option->{$opt} = 1;\n}\n\n# hotplug changes in [PENDING]\n# $selection hash can be used to only apply specified options, for\n# example: { cores => 1 } (only apply changed 'cores')\n# $errors ref is used to return error messages\nsub vmconfig_hotplug_pending {\n    my ($vmid, $conf, $storecfg, $selection, $errors) = @_;\n\n    my $defaults = load_defaults();\n    my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n    my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf);\n\n    # commit values which do not have any impact on running VM first\n    # Note: those option cannot raise errors, we we do not care about\n    # $selection and always apply them.\n\n    my $add_error = sub {\n        my ($opt, $msg) = @_;\n        $errors->{$opt} = \"hotplug problem - $msg\";\n    };\n\n    my $cloudinit_pending_properties = PVE::QemuServer::cloudinit_pending_properties();\n\n    my $cloudinit_record_changed = sub {\n        my ($conf, $opt, $old, $new) = @_;\n        return if !$cloudinit_pending_properties->{$opt};\n\n        my $ci = ($conf->{'special-sections'}->{cloudinit} //= {});\n\n        my $recorded = $ci->{$opt};\n        my %added = map { $_ => 1 } PVE::Tools::split_list(delete($ci->{added}) // '');\n\n        if (defined($new)) {\n            if (defined($old)) {\n                # an existing value is being modified\n                if (defined($recorded)) {\n                    # the value was already not in sync\n                    if ($new eq $recorded) {\n                        # a value is being reverted to the cloud-init state:\n                        delete $ci->{$opt};\n                        delete $added{$opt};\n                    } else {\n                        # the value was changed multiple times, do nothing\n                    }\n                } elsif ($added{$opt}) {\n                    # the value had been marked as added and is being changed, do nothing\n                } else {\n                    # the value is new, record it:\n                    $ci->{$opt} = $old;\n                }\n            } else {\n                # a new value is being added\n                if (defined($recorded)) {\n                    # it was already not in sync\n                    if ($new eq $recorded) {\n                        # a value is being reverted to the cloud-init state:\n                        delete $ci->{$opt};\n                        delete $added{$opt};\n                    } else {\n                        # the value had temporarily been removed, do nothing\n                    }\n                } elsif ($added{$opt}) {\n                    # the value had been marked as added already, do nothing\n                } else {\n                    # the value is new, add it\n                    $added{$opt} = 1;\n                }\n            }\n        } elsif (!defined($old)) {\n            # a non-existent value is being removed? ignore...\n        } else {\n            # a value is being deleted\n            if (defined($recorded)) {\n                # a value was already recorded, just keep it\n            } elsif ($added{$opt}) {\n                # the value was marked as added, remove it\n                delete $added{$opt};\n            } else {\n                # a previously unrecorded value is being removed, record the old value:\n                $ci->{$opt} = $old;\n            }\n        }\n\n        my $added = join(',', sort keys %added);\n        $ci->{added} = $added if length($added);\n    };\n\n    my $changes = 0;\n    foreach my $opt (keys %{ $conf->{pending} }) { # add/change\n        if ($fast_plug_option->{$opt}) {\n            my $new = delete $conf->{pending}->{$opt};\n            $cloudinit_record_changed->($conf, $opt, $conf->{$opt}, $new);\n            $conf->{$opt} = $new;\n            $changes = 1;\n        }\n    }\n\n    if ($changes) {\n        PVE::QemuConfig->write_config($vmid, $conf);\n    }\n\n    my $hotplug_features =\n        parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');\n\n    my $usb_hotplug;\n    my $usb_was_unplugged = 0;\n    my $is_usb_hotplug_supported = sub {\n        return $usb_hotplug if defined($usb_hotplug);\n        my $ostype = $conf->{ostype};\n        my $version = extract_version($machine_type, get_running_qemu_version($vmid));\n        $usb_hotplug =\n            $hotplug_features->{usb}\n            && min_version($version, 7, 1)\n            && defined($ostype)\n            && ($ostype eq 'l26' || windows_version($ostype) > 7) ? 1 : 0;\n        return $usb_hotplug;\n    };\n\n    my $cgroup = PVE::QemuServer::CGroup->new($vmid);\n    my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});\n\n    foreach my $opt (sort keys %$pending_delete_hash) {\n        next if $selection && !$selection->{$opt};\n        my $force = $pending_delete_hash->{$opt}->{force};\n        eval {\n            if ($opt eq 'hotplug') {\n                die \"skip\\n\" if ($conf->{hotplug} =~ /(cpu|memory)/);\n            } elsif ($opt eq 'tablet') {\n                die \"skip\\n\" if !$hotplug_features->{usb};\n                if ($defaults->{tablet}) {\n                    vm_deviceplug($storecfg, $conf, $vmid, 'tablet', $arch, $machine_type);\n                    vm_deviceplug($storecfg, $conf, $vmid, 'keyboard', $arch, $machine_type)\n                        if $arch eq 'aarch64';\n                } else {\n                    vm_deviceunplug($vmid, $conf, 'tablet');\n                    vm_deviceunplug($vmid, $conf, 'keyboard') if $arch eq 'aarch64';\n                }\n            } elsif ($opt =~ m/^usb(\\d+)$/) {\n                my $index = $1;\n                die \"skip\\n\" if !$is_usb_hotplug_supported->();\n                vm_deviceunplug($vmid, $conf, \"usbredirdev$index\"); # if it's a spice port\n                vm_deviceunplug($vmid, $conf, $opt);\n                $usb_was_unplugged = 1;\n            } elsif ($opt eq 'vcpus') {\n                die \"skip\\n\" if !$hotplug_features->{cpu};\n                qemu_cpu_hotplug($vmid, $conf, undef);\n            } elsif ($opt eq 'balloon') {\n                # enable balloon device is not hotpluggable\n                die \"skip\\n\" if defined($conf->{balloon}) && $conf->{balloon} == 0;\n                # here we reset the ballooning value to memory\n                my $balloon = get_current_memory($conf->{memory});\n                mon_cmd($vmid, \"balloon\", value => $balloon * 1024 * 1024);\n            } elsif ($fast_plug_option->{$opt}) {\n                # do nothing\n            } elsif ($opt =~ m/^net(\\d+)$/) {\n                die \"skip\\n\" if !$hotplug_features->{network};\n                vm_deviceunplug($vmid, $conf, $opt);\n                my $net = PVE::QemuServer::Network::parse_net($conf->{$opt});\n                PVE::Network::SDN::Vnets::del_ips_from_mac(\n                    $net->{bridge},\n                    $net->{macaddr},\n                    $conf->{name},\n                );\n            } elsif (is_valid_drivename($opt)) {\n                die \"skip\\n\"\n                    if !$hotplug_features->{disk} || $opt =~ m/(efidisk|ide|sata|tpmstate)(\\d+)/;\n                vm_deviceunplug($vmid, $conf, $opt);\n                vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);\n            } elsif ($opt =~ m/^memory$/) {\n                die \"skip\\n\" if !$hotplug_features->{memory};\n                PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf);\n            } elsif ($opt eq 'cpuunits') {\n                $cgroup->change_cpu_shares(undef);\n            } elsif ($opt eq 'cpulimit') {\n                $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values\n            } else {\n                die \"skip\\n\";\n            }\n        };\n        if (my $err = $@) {\n            &$add_error($opt, $err) if $err ne \"skip\\n\";\n        } else {\n            my $old = delete $conf->{$opt};\n            $cloudinit_record_changed->($conf, $opt, $old, undef);\n            PVE::QemuConfig->remove_from_pending_delete($conf, $opt);\n        }\n    }\n\n    my $cloudinit_opt;\n    foreach my $opt (keys %{ $conf->{pending} }) {\n        next if $selection && !$selection->{$opt};\n        my $value = $conf->{pending}->{$opt};\n        eval {\n            if ($opt eq 'hotplug') {\n                die \"skip\\n\"\n                    if ($value =~ /memory/) || ($conf->{hotplug} && $conf->{hotplug} =~ /memory/);\n                die \"skip\\n\"\n                    if ($value =~ /cpu/) || ($conf->{hotplug} && $conf->{hotplug} =~ /cpu/);\n            } elsif ($opt eq 'tablet') {\n                die \"skip\\n\" if !$hotplug_features->{usb};\n                if ($value == 1) {\n                    vm_deviceplug($storecfg, $conf, $vmid, 'tablet', $arch, $machine_type);\n                    vm_deviceplug($storecfg, $conf, $vmid, 'keyboard', $arch, $machine_type)\n                        if $arch eq 'aarch64';\n                } elsif ($value == 0) {\n                    vm_deviceunplug($vmid, $conf, 'tablet');\n                    vm_deviceunplug($vmid, $conf, 'keyboard') if $arch eq 'aarch64';\n                }\n            } elsif ($opt =~ m/^usb(\\d+)$/) {\n                my $index = $1;\n                die \"skip\\n\" if !$is_usb_hotplug_supported->();\n                my $d = eval { parse_property_string('pve-qm-usb', $value) };\n                my $id = $opt;\n                if ($d->{host} =~ m/^spice$/i) {\n                    $id = \"usbredirdev$index\";\n                }\n                qemu_usb_hotplug($storecfg, $conf, $vmid, $id, $d, $arch, $machine_type);\n            } elsif ($opt eq 'vcpus') {\n                die \"skip\\n\" if !$hotplug_features->{cpu};\n                qemu_cpu_hotplug($vmid, $conf, $value);\n            } elsif ($opt eq 'balloon') {\n                # enable/disable ballooning device is not hotpluggable\n                my $old_balloon_enabled = !!(!defined($conf->{balloon}) || $conf->{balloon});\n                my $new_balloon_enabled =\n                    !!(!defined($conf->{pending}->{balloon}) || $conf->{pending}->{balloon});\n                die \"skip\\n\" if $old_balloon_enabled != $new_balloon_enabled;\n\n                # allow manual ballooning if shares is set to zero\n                if ((defined($conf->{shares}) && ($conf->{shares} == 0))) {\n                    my $memory = get_current_memory($conf->{memory});\n                    my $balloon = $conf->{pending}->{balloon} || $memory;\n                    mon_cmd($vmid, \"balloon\", value => $balloon * 1024 * 1024);\n                }\n            } elsif ($opt =~ m/^net(\\d+)$/) {\n                # some changes can be done without hotplug\n                vmconfig_update_net(\n                    $storecfg,\n                    $conf,\n                    $hotplug_features->{network},\n                    $vmid,\n                    $opt,\n                    $value,\n                    $arch,\n                    $machine_type,\n                );\n            } elsif (is_valid_drivename($opt)) {\n                die \"skip\\n\" if $opt eq 'efidisk0' || $opt eq 'tpmstate0';\n                # some changes can be done without hotplug\n                my $drive = parse_drive($opt, $value);\n                if (drive_is_cloudinit($drive)) {\n                    $cloudinit_opt = [$opt, $drive];\n                    # apply all the other changes first, then generate the cloudinit disk\n                    die \"skip\\n\";\n                }\n                vmconfig_update_disk(\n                    $storecfg,\n                    $conf,\n                    $hotplug_features->{disk},\n                    $vmid,\n                    $opt,\n                    $value,\n                    $arch,\n                    $machine_type,\n                );\n            } elsif ($opt =~ m/^memory$/) { #dimms\n                die \"skip\\n\" if !$hotplug_features->{memory};\n                $value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $value);\n            } elsif ($opt eq 'cpuunits') {\n                my $new_cpuunits = PVE::CGroup::clamp_cpu_shares($conf->{pending}->{$opt}); #clamp\n                $cgroup->change_cpu_shares($new_cpuunits);\n            } elsif ($opt eq 'cpulimit') {\n                my $cpulimit =\n                    $conf->{pending}->{$opt} == 0 ? undef : int($conf->{pending}->{$opt} * 100);\n                $cgroup->change_cpu_quota($cpulimit, undef);\n            } elsif ($opt eq 'agent') {\n                vmconfig_update_agent($conf, $opt, $value);\n            } else {\n                die \"skip\\n\"; # skip non-hot-pluggable options\n            }\n        };\n        if (my $err = $@) {\n            &$add_error($opt, $err) if $err ne \"skip\\n\";\n        } else {\n            $cloudinit_record_changed->($conf, $opt, $conf->{$opt}, $value);\n            $conf->{$opt} = $value;\n            delete $conf->{pending}->{$opt};\n        }\n    }\n\n    if (defined($cloudinit_opt)) {\n        my ($opt, $drive) = @$cloudinit_opt;\n        my $value = $conf->{pending}->{$opt};\n        eval {\n            my $temp = { %$conf, $opt => $value };\n            PVE::QemuServer::Cloudinit::apply_cloudinit_config($temp, $vmid);\n            vmconfig_update_disk(\n                $storecfg,\n                $conf,\n                $hotplug_features->{disk},\n                $vmid,\n                $opt,\n                $value,\n                $arch,\n                $machine_type,\n            );\n        };\n        if (my $err = $@) {\n            &$add_error($opt, $err) if $err ne \"skip\\n\";\n        } else {\n            $conf->{$opt} = $value;\n            delete $conf->{pending}->{$opt};\n        }\n    }\n\n    # unplug xhci controller if no usb device is left\n    if ($usb_was_unplugged) {\n        my $has_usb = 0;\n        for (my $i = 0; $i < $PVE::QemuServer::USB::MAX_USB_DEVICES; $i++) {\n            next if !defined($conf->{\"usb$i\"});\n            $has_usb = 1;\n            last;\n        }\n        if (!$has_usb) {\n            vm_deviceunplug($vmid, $conf, 'xhci');\n        }\n    }\n\n    PVE::QemuConfig->write_config($vmid, $conf);\n\n    if ($hotplug_features->{cloudinit} && PVE::QemuServer::Cloudinit::has_changes($conf)) {\n        PVE::QemuServer::vmconfig_update_cloudinit_drive($storecfg, $conf, $vmid);\n    }\n}\n\nsub try_deallocate_drive {\n    my ($storecfg, $vmid, $conf, $key, $drive, $rpcenv, $authuser, $force) = @_;\n\n    if (($force || $key =~ /^unused/) && !drive_is_cdrom($drive, 1)) {\n        my $volid = $drive->{file};\n        if (vm_is_volid_owner($storecfg, $vmid, $volid)) {\n            my $sid = PVE::Storage::parse_volume_id($volid);\n            $rpcenv->check($authuser, \"/storage/$sid\", ['Datastore.AllocateSpace']);\n\n            # check if the disk is really unused\n            die \"unable to delete '$volid' - volume is still in use (snapshot?)\\n\"\n                if PVE::QemuServer::Drive::is_volume_in_use($storecfg, $conf, $key, $volid);\n            PVE::Storage::vdisk_free($storecfg, $volid);\n            return 1;\n        } else {\n            # If vm is not owner of this disk remove from config\n            return 1;\n        }\n    }\n\n    return;\n}\n\nsub vmconfig_delete_or_detach_drive {\n    my ($vmid, $storecfg, $conf, $opt, $force) = @_;\n\n    my $drive = parse_drive($opt, $conf->{$opt});\n\n    my $rpcenv = PVE::RPCEnvironment::get();\n    my $authuser = $rpcenv->get_user();\n\n    if ($force) {\n        $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);\n        try_deallocate_drive($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser, $force);\n    } else {\n        vmconfig_register_unused_drive($storecfg, $vmid, $conf, $drive);\n    }\n}\n\nsub vmconfig_apply_pending {\n    my ($vmid, $conf, $storecfg, $errors, $skip_cloud_init) = @_;\n\n    return if !scalar(keys %{ $conf->{pending} });\n\n    my $add_apply_error = sub {\n        my ($opt, $msg) = @_;\n        my $err_msg = \"unable to apply pending change $opt : $msg\";\n        $errors->{$opt} = $err_msg;\n        warn $err_msg;\n    };\n\n    # cold plug\n\n    my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});\n    foreach my $opt (sort keys %$pending_delete_hash) {\n        my $force = $pending_delete_hash->{$opt}->{force};\n        eval {\n            if ($opt =~ m/^unused/) {\n                die \"internal error\";\n            } elsif (defined($conf->{$opt}) && is_valid_drivename($opt)) {\n                vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);\n            } elsif (defined($conf->{$opt}) && $opt =~ m/^net\\d+$/) {\n                my $net = PVE::QemuServer::Network::parse_net($conf->{$opt});\n                eval {\n                    PVE::Network::SDN::Vnets::del_ips_from_mac(\n                        $net->{bridge},\n                        $net->{macaddr},\n                        $conf->{name},\n                    );\n                };\n                warn if $@;\n            }\n        };\n        if (my $err = $@) {\n            $add_apply_error->($opt, $err);\n        } else {\n            PVE::QemuConfig->remove_from_pending_delete($conf, $opt);\n            delete $conf->{$opt};\n        }\n    }\n\n    PVE::QemuConfig->cleanup_pending($conf);\n\n    my $generate_cloudinit = $skip_cloud_init ? 0 : undef;\n\n    foreach my $opt (keys %{ $conf->{pending} }) { # add/change\n        next if $opt eq 'delete'; # just to be sure\n        eval {\n            if (defined($conf->{$opt}) && is_valid_drivename($opt)) {\n                my $old_drive = parse_drive($opt, $conf->{$opt});\n                vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);\n                if ($opt eq 'efidisk0') {\n                    my $new_drive = parse_drive($opt, $conf->{pending}->{$opt});\n                    PVE::QemuServer::OVMF::drive_change(\n                        $storecfg,\n                        $vmid,\n                        $old_drive,\n                        $new_drive,\n                    );\n                    $conf->{pending}->{$opt} = print_drive($new_drive);\n                }\n            } elsif (defined($conf->{pending}->{$opt}) && $opt =~ m/^net\\d+$/) {\n                my $new_net = PVE::QemuServer::Network::parse_net($conf->{pending}->{$opt});\n                if ($conf->{$opt}) {\n                    my $old_net = PVE::QemuServer::Network::parse_net($conf->{$opt});\n\n                    if (\n                        defined($old_net->{bridge})\n                        && defined($old_net->{macaddr})\n                        && (safe_string_ne($old_net->{bridge}, $new_net->{bridge})\n                            || safe_string_ne($old_net->{macaddr}, $new_net->{macaddr}))\n                    ) {\n                        PVE::Network::SDN::Vnets::del_ips_from_mac(\n                            $old_net->{bridge},\n                            $old_net->{macaddr},\n                            $conf->{name},\n                        );\n                        PVE::Network::SDN::Vnets::add_next_free_cidr(\n                            $new_net->{bridge},\n                            $conf->{name},\n                            $new_net->{macaddr},\n                            $vmid,\n                            undef,\n                            1,\n                        );\n                    }\n                } else {\n                    PVE::Network::SDN::Vnets::add_next_free_cidr(\n                        $new_net->{bridge},\n                        $conf->{name},\n                        $new_net->{macaddr},\n                        $vmid,\n                        undef,\n                        1,\n                    );\n                }\n            }\n        };\n        if (my $err = $@) {\n            $add_apply_error->($opt, $err);\n        } else {\n\n            if (is_valid_drivename($opt)) {\n                my $drive = parse_drive($opt, $conf->{pending}->{$opt});\n                $generate_cloudinit //= 1 if drive_is_cloudinit($drive);\n            }\n\n            $conf->{$opt} = delete $conf->{pending}->{$opt};\n        }\n    }\n\n    # write all changes at once to avoid unnecessary i/o\n    PVE::QemuConfig->write_config($vmid, $conf);\n    if ($generate_cloudinit) {\n        if (PVE::QemuServer::Cloudinit::apply_cloudinit_config($conf, $vmid)) {\n            # After successful generation and if there were changes to be applied, update the\n            # config to drop the 'cloudinit' special section.\n            PVE::QemuConfig->write_config($vmid, $conf);\n        }\n    }\n}\n\nsub vmconfig_update_net {\n    my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $arch, $machine_type) = @_;\n\n    my $newnet = PVE::QemuServer::Network::parse_net($value);\n\n    if ($conf->{$opt}) {\n        my $oldnet = PVE::QemuServer::Network::parse_net($conf->{$opt});\n\n        if (\n            safe_string_ne($oldnet->{model}, $newnet->{model})\n            || safe_string_ne($oldnet->{macaddr}, $newnet->{macaddr})\n            || safe_num_ne($oldnet->{queues}, $newnet->{queues})\n            || safe_num_ne($oldnet->{mtu}, $newnet->{mtu})\n            || !($newnet->{bridge} && $oldnet->{bridge})\n        ) { # bridge/nat mode change\n\n            # for non online change, we try to hot-unplug\n            die \"skip\\n\" if !$hotplug;\n            vm_deviceunplug($vmid, $conf, $opt);\n\n            PVE::Network::SDN::Vnets::del_ips_from_mac(\n                $oldnet->{bridge},\n                $oldnet->{macaddr},\n                $conf->{name},\n            );\n        } else {\n\n            die \"internal error\" if $opt !~ m/net(\\d+)/;\n            my $iface = \"tap${vmid}i$1\";\n\n            if (\n                safe_string_ne($oldnet->{bridge}, $newnet->{bridge})\n                || safe_num_ne($oldnet->{tag}, $newnet->{tag})\n                || safe_string_ne($oldnet->{trunks}, $newnet->{trunks})\n                || safe_num_ne($oldnet->{firewall}, $newnet->{firewall})\n            ) {\n                PVE::Network::tap_unplug($iface);\n\n                #set link_down in guest if bridge or vlan change to notify guest (dhcp renew for example)\n                if (\n                    safe_string_ne($oldnet->{bridge}, $newnet->{bridge})\n                    || safe_num_ne($oldnet->{tag}, $newnet->{tag})\n                ) {\n                    qemu_set_link_status($vmid, $opt, 0);\n                }\n\n                if (safe_string_ne($oldnet->{bridge}, $newnet->{bridge})) {\n                    PVE::Network::SDN::Vnets::del_ips_from_mac(\n                        $oldnet->{bridge},\n                        $oldnet->{macaddr},\n                        $conf->{name},\n                    );\n                    PVE::Network::SDN::Vnets::add_next_free_cidr(\n                        $newnet->{bridge}, $conf->{name}, $newnet->{macaddr}, $vmid, undef, 1,\n                    );\n                }\n\n                PVE::QemuServer::Network::tap_plug(\n                    $iface,\n                    $newnet->{bridge},\n                    $newnet->{tag},\n                    $newnet->{firewall},\n                    $newnet->{trunks},\n                    $newnet->{rate},\n                );\n\n            } elsif (safe_num_ne($oldnet->{rate}, $newnet->{rate})) {\n                # Rate can be applied on its own but any change above needs to\n                # include the rate in tap_plug since OVS resets everything.\n                PVE::Network::tap_rate_limit($iface, $newnet->{rate});\n            }\n\n            # set link_down on changed bridge/tag as well, because we detach the\n            # network device in the section above if the bridge or tag changed\n            if (\n                safe_string_ne($oldnet->{link_down}, $newnet->{link_down})\n                || safe_string_ne($oldnet->{bridge}, $newnet->{bridge})\n                || safe_num_ne($oldnet->{tag}, $newnet->{tag})\n            ) {\n                qemu_set_link_status($vmid, $opt, !$newnet->{link_down});\n            }\n\n            return 1;\n        }\n    }\n\n    if ($hotplug) {\n        PVE::Network::SDN::Vnets::add_next_free_cidr(\n            $newnet->{bridge}, $conf->{name}, $newnet->{macaddr}, $vmid, undef, 1,\n        );\n        PVE::Network::SDN::Vnets::add_dhcp_mapping(\n            $newnet->{bridge}, $newnet->{macaddr}, $vmid, $conf->{name},\n        );\n        vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet, $arch, $machine_type);\n    } else {\n        die \"skip\\n\";\n    }\n}\n\nsub vmconfig_update_agent {\n    my ($conf, $opt, $value) = @_;\n\n    die \"skip\\n\" if !$conf->{$opt};\n\n    my $hotplug_options = { fstrim_cloned_disks => 1 };\n\n    my $old_agent = parse_guest_agent($conf);\n    my $agent = parse_guest_agent({ $opt => $value });\n\n    for my $option (keys %$agent) { # added/changed options\n        next if defined($hotplug_options->{$option});\n        die \"skip\\n\" if safe_string_ne($agent->{$option}, $old_agent->{$option});\n    }\n\n    for my $option (keys %$old_agent) { # removed options\n        next if defined($hotplug_options->{$option});\n        die \"skip\\n\" if safe_string_ne($old_agent->{$option}, $agent->{$option});\n    }\n\n    return; # either no actual change (e.g., format string reordered) or just hotpluggable changes\n}\n\nsub vmconfig_update_disk {\n    my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $arch, $machine_type) = @_;\n\n    my $drive = parse_drive($opt, $value);\n\n    if ($conf->{$opt} && (my $old_drive = parse_drive($opt, $conf->{$opt}))) {\n        my $media = $drive->{media} || 'disk';\n        my $oldmedia = $old_drive->{media} || 'disk';\n        die \"unable to change media type\\n\" if $media ne $oldmedia;\n\n        if (!drive_is_cdrom($old_drive)) {\n\n            if ($drive->{file} ne $old_drive->{file}) {\n\n                die \"skip\\n\" if !$hotplug;\n\n                # unplug and register as unused\n                vm_deviceunplug($vmid, $conf, $opt);\n                vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);\n\n            } else {\n                # update existing disk\n\n                # skip non hotpluggable value\n                if (\n                    safe_string_ne($drive->{aio}, $old_drive->{aio})\n                    || safe_string_ne($drive->{discard}, $old_drive->{discard})\n                    || safe_string_ne($drive->{iothread}, $old_drive->{iothread})\n                    || safe_string_ne($drive->{queues}, $old_drive->{queues})\n                    || safe_string_ne($drive->{product}, $old_drive->{product})\n                    || safe_string_ne($drive->{cache}, $old_drive->{cache})\n                    || safe_string_ne($drive->{ssd}, $old_drive->{ssd})\n                    || safe_string_ne($drive->{vendor}, $old_drive->{vendor})\n                    || safe_string_ne($drive->{ro}, $old_drive->{ro})\n                ) {\n                    die \"skip\\n\";\n                }\n\n                # apply throttle\n                if (\n                    safe_num_ne($drive->{mbps}, $old_drive->{mbps})\n                    || safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd})\n                    || safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr})\n                    || safe_num_ne($drive->{iops}, $old_drive->{iops})\n                    || safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd})\n                    || safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr})\n                    || safe_num_ne($drive->{mbps_max}, $old_drive->{mbps_max})\n                    || safe_num_ne($drive->{mbps_rd_max}, $old_drive->{mbps_rd_max})\n                    || safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max})\n                    || safe_num_ne($drive->{iops_max}, $old_drive->{iops_max})\n                    || safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max})\n                    || safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max})\n                    || safe_num_ne($drive->{bps_max_length}, $old_drive->{bps_max_length})\n                    || safe_num_ne($drive->{bps_rd_max_length}, $old_drive->{bps_rd_max_length})\n                    || safe_num_ne($drive->{bps_wr_max_length}, $old_drive->{bps_wr_max_length})\n                    || safe_num_ne($drive->{iops_max_length}, $old_drive->{iops_max_length})\n                    || safe_num_ne($drive->{iops_rd_max_length},\n                        $old_drive->{iops_rd_max_length})\n                    || safe_num_ne($drive->{iops_wr_max_length},\n                        $old_drive->{iops_wr_max_length})\n                ) {\n\n                    PVE::QemuServer::Blockdev::set_io_throttle(\n                        $vmid,\n                        \"drive-$opt\",\n                        ($drive->{mbps} || 0) * 1024 * 1024,\n                        ($drive->{mbps_rd} || 0) * 1024 * 1024,\n                        ($drive->{mbps_wr} || 0) * 1024 * 1024,\n                        $drive->{iops} || 0,\n                        $drive->{iops_rd} || 0,\n                        $drive->{iops_wr} || 0,\n                        ($drive->{mbps_max} || 0) * 1024 * 1024,\n                        ($drive->{mbps_rd_max} || 0) * 1024 * 1024,\n                        ($drive->{mbps_wr_max} || 0) * 1024 * 1024,\n                        $drive->{iops_max} || 0,\n                        $drive->{iops_rd_max} || 0,\n                        $drive->{iops_wr_max} || 0,\n                        $drive->{bps_max_length} || 1,\n                        $drive->{bps_rd_max_length} || 1,\n                        $drive->{bps_wr_max_length} || 1,\n                        $drive->{iops_max_length} || 1,\n                        $drive->{iops_rd_max_length} || 1,\n                        $drive->{iops_wr_max_length} || 1,\n                    );\n\n                }\n\n                return 1;\n            }\n\n        } else { # cdrom\n            eval { PVE::QemuServer::Blockdev::change_medium($storecfg, $vmid, $opt, $drive); };\n            my $err = $@;\n\n            if ($drive->{file} eq 'none' && drive_is_cloudinit($old_drive)) {\n                vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);\n            }\n\n            die $err if $err;\n\n            return 1;\n        }\n    }\n\n    die \"skip\\n\" if !$hotplug || $opt =~ m/(ide|sata)(\\d+)/;\n    # hotplug new disks\n    PVE::Storage::activate_volumes($storecfg, [$drive->{file}]) if $drive->{file} !~ m|^/dev/.+|;\n    vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive, $arch, $machine_type);\n}\n\nsub vmconfig_update_cloudinit_drive {\n    my ($storecfg, $conf, $vmid) = @_;\n\n    my $cloudinit_ds = undef;\n    my $cloudinit_drive = undef;\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n            if (PVE::QemuServer::drive_is_cloudinit($drive)) {\n                $cloudinit_ds = $ds;\n                $cloudinit_drive = $drive;\n            }\n        },\n    );\n\n    return if !$cloudinit_drive;\n\n    if (PVE::QemuServer::Cloudinit::apply_cloudinit_config($conf, $vmid)) {\n        PVE::QemuConfig->write_config($vmid, $conf);\n    }\n\n    my $running = PVE::QemuServer::check_running($vmid);\n\n    if ($running) {\n        PVE::QemuServer::Blockdev::change_medium($storecfg, $vmid, $cloudinit_ds, $cloudinit_drive);\n    }\n}\n\n# called in locked context by incoming migration\nsub vm_migrate_get_nbd_disks {\n    my ($storecfg, $conf, $replicated_volumes) = @_;\n\n    my $local_volumes = {};\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            return if drive_is_cdrom($drive);\n            return if $ds eq 'tpmstate0';\n\n            my $volid = $drive->{file};\n\n            return if !$volid;\n\n            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n            return if $scfg->{shared};\n\n            my $format = checked_volume_format($storecfg, $volid);\n\n            # replicated disks re-use existing state via bitmap\n            my $use_existing = $replicated_volumes->{$volid} ? 1 : 0;\n            $local_volumes->{$ds} = [$volid, $storeid, $drive, $use_existing, $format];\n        },\n    );\n    return $local_volumes;\n}\n\n# called in locked context by incoming migration\nsub vm_migrate_alloc_nbd_disks {\n    my ($storecfg, $vmid, $source_volumes, $storagemap) = @_;\n\n    my $nbd = {};\n    foreach my $opt (sort keys %$source_volumes) {\n        my ($volid, $storeid, $drive, $use_existing, $format) = @{ $source_volumes->{$opt} };\n\n        if ($use_existing) {\n            $nbd->{$opt}->{drivestr} = print_drive($drive);\n            $nbd->{$opt}->{volid} = $volid;\n            $nbd->{$opt}->{replicated} = 1;\n            next;\n        }\n\n        $storeid = PVE::JSONSchema::map_id($storagemap, $storeid);\n\n        # order of precedence, filtered by whether storage supports it:\n        # 1. explicit requested format\n        # 2. default format of storage\n        $format = PVE::Storage::resolve_format_hint($storecfg, $storeid, $format);\n\n        my $size = $drive->{size} / 1024;\n        my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, $size);\n        my $newdrive = $drive;\n        $newdrive->{format} = $format;\n        $newdrive->{file} = $newvolid;\n        my $drivestr = print_drive($newdrive);\n        $nbd->{$opt}->{drivestr} = $drivestr;\n        $nbd->{$opt}->{volid} = $newvolid;\n    }\n\n    return $nbd;\n}\n\nmy sub remove_left_over_vmstate_opts {\n    my ($vmid, $conf) = @_;\n\n    my $found;\n    for my $opt (qw(running-nets-host-mtu runningmachine runningcpu)) {\n        if (defined($conf->{$opt})) {\n            print \"No VM state set, removing left-over option '$opt'\\n\";\n            delete $conf->{$opt};\n            $found = 1;\n        }\n    }\n    PVE::QemuConfig->write_config($vmid, $conf) if $found;\n}\n\nsub generate_storage_hints {\n    my ($conf, $plugin_may_deactivate_volume) = @_;\n\n    my $hints = {};\n\n    if (PVE::Storage::Plugin::is_hint_supported('guest-is-windows')) {\n        $hints->{'guest-is-windows'} = int(!!windows_version($conf->{ostype}));\n    }\n    if (PVE::Storage::Plugin::is_hint_supported('plugin-may-deactivate-volume')) {\n        $hints->{'plugin-may-deactivate-volume'} = $plugin_may_deactivate_volume || 0;\n    }\n\n    return $hints;\n}\n\nmy sub check_efi_vars {\n    my ($storecfg, $vmid, $conf) = @_;\n\n    return if PVE::QemuConfig->is_template($conf);\n    return if !$conf->{efidisk0};\n\n    my $efidisk = parse_drive('efidisk0', $conf->{efidisk0});\n    if (PVE::QemuServer::OVMF::should_enroll_ms_2023_cert($efidisk)) {\n        # TODO: make the first print a log_warn with PVE 9.2 to make it more noticeable!\n        print \"EFI disk without 'ms-cert=2023k' option, suggesting that not all UEFI 2023\\n\";\n        print \"certificates from Microsoft are enrolled yet. The UEFI 2011 certificates expire\\n\";\n        print\n            \"in June 2026! The new certificates are required for secure boot update for Windows\\n\";\n        print \"and common Linux distributions. Use 'Disk Action > Enroll Updated Certificates'\\n\";\n        print \"in the UI or, while the VM is shut down, run 'qm enroll-efi-keys $vmid' to enroll\\n\";\n        print \"the new certificates.\\n\\n\";\n        print \"For Windows with BitLocker, run the following command inside Powershell:\\n\";\n        print \"  manage-bde -protectors -disable <drive>\\n\";\n        print \"for each drive with BitLocker (for example, <drive> could be 'C:').\\n\";\n        print \"This is required for each drive with BitLocker before proceeding with enrollment.\\n\";\n        print \"Otherwise, you will be prompted for the BitLocker recovery key on the next boot.\\n\";\n    }\n\n    return;\n}\n\nmy $log_filter_catch_outdated_zen5_firmware = sub {\n    my ($line) = @_;\n    print \"$line\\n\";\n    if ($line =~ m/host doesn't support requested feature:.*rdseed\\s*\\[bit 18\\]/) {\n        log_warn(\"On Zen 5 systems, the rdseed CPU flag might not be available when the CPU\"\n            . \" firmware is outdated. See:\\n\"\n            . \"https://security-tracker.debian.org/tracker/CVE-2025-62626\\n\"\n            . \"https://pve.proxmox.com/pve-docs/chapter-sysadmin.html#sysadmin_firmware_cpu\");\n    }\n};\n\n# see vm_start_nolock for parameters, additionally:\n# migrate_opts:\n#   storagemap = parsed storage map for allocating NBD disks\nsub vm_start {\n    my ($storecfg, $vmid, $params, $migrate_opts) = @_;\n\n    return PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n            my $conf = PVE::QemuConfig->load_config($vmid, $migrate_opts->{migratedfrom});\n\n            die \"you can't start a vm if it's a template\\n\"\n                if !$params->{skiptemplate} && PVE::QemuConfig->is_template($conf);\n\n            my $has_suspended_lock = PVE::QemuConfig->has_lock($conf, 'suspended');\n            my $has_backup_lock = PVE::QemuConfig->has_lock($conf, 'backup');\n\n            my $running = check_running($vmid, undef, $migrate_opts->{migratedfrom});\n\n            if ($has_backup_lock && $running) {\n                # a backup is currently running, attempt to start the guest in the\n                # existing QEMU instance\n                return PVE::QemuServer::RunState::vm_resume($vmid);\n            }\n\n            PVE::QemuConfig->check_lock($conf)\n                if !($params->{skiplock} || $has_suspended_lock);\n\n            $params->{resume} = $has_suspended_lock || defined($conf->{vmstate});\n\n            die \"VM $vmid already running\\n\" if $running;\n\n            if (my $storagemap = $migrate_opts->{storagemap}) {\n                my $replicated = $migrate_opts->{replicated_volumes};\n                my $disks = vm_migrate_get_nbd_disks($storecfg, $conf, $replicated);\n                $migrate_opts->{nbd} =\n                    vm_migrate_alloc_nbd_disks($storecfg, $vmid, $disks, $storagemap);\n\n                foreach my $opt (keys %{ $migrate_opts->{nbd} }) {\n                    $conf->{$opt} = $migrate_opts->{nbd}->{$opt}->{drivestr};\n                }\n            }\n\n            return vm_start_nolock($storecfg, $vmid, $conf, $params, $migrate_opts);\n        },\n    );\n}\n\n# params:\n#   statefile => 'tcp', 'unix' for migration or path/volid for RAM state\n#   skiplock => 0/1, skip checking for config lock\n#   skiptemplate => 0/1, skip checking whether VM is template\n#   forcemachine => to force QEMU machine (rollback/migration)\n#   forcecpu => a QEMU '-cpu' argument string to override get_cpu_options\n#   timeout => in seconds\n#   paused => start VM in paused state (backup)\n#   resume => resume from hibernation\n#   live-restore-backing => {\n#      sata0 => {\n#          name => blockdev-name,\n#          blockdev => \"arg to the -blockdev command instantiating device named 'name'\",\n#      },\n#      virtio2 => ...\n#   }\n#   nets-host-mtu => Used for migration compat. List of VirtIO network devices and their effective\n#       host_mtu setting according to the QEMU object model on the source side of the migration.\n# migrate_opts:\n#   nbd => volumes for NBD exports (vm_migrate_alloc_nbd_disks)\n#   migratedfrom => source node\n#   spice_ticket => used for spice migration, passed via tunnel/stdin\n#   network => CIDR of migration network\n#   type => secure/insecure - tunnel over encrypted connection or plain-text\n#   nbd_proto_version => int, 0 for TCP, 1 for UNIX\n#   replicated_volumes => which volids should be re-used with bitmaps for nbd migration\n#   offline_volumes => new volids of offline migrated disks like tpmstate and cloudinit, not yet\n#       contained in config\n#   with_conntrack_state => whether to start the dbus-vmstate helper for conntrack state migration\nsub vm_start_nolock {\n    my ($storecfg, $vmid, $conf, $params, $migrate_opts) = @_;\n\n    my $statefile = $params->{statefile};\n    my $resume = $params->{resume};\n\n    my $migratedfrom = $migrate_opts->{migratedfrom};\n    my $migration_type = $migrate_opts->{type};\n    my $nbd_protocol_version = $migrate_opts->{nbd_proto_version} // 0;\n\n    # clean up leftover reboot request files\n    eval { clear_reboot_request($vmid); };\n    warn $@ if $@;\n\n    # terminate left-over storage daemon if still running\n    if (my $pid = PVE::QemuServer::Helpers::qsd_running_locally($vmid)) {\n        log_warn(\"left-over QEMU storage daemon for $vmid running with PID $pid - terminating now\");\n        PVE::QemuServer::QSD::quit($vmid);\n    }\n\n    if (!$statefile && !$resume && scalar(keys %{ $conf->{pending} })) {\n        vmconfig_apply_pending($vmid, $conf, $storecfg);\n        $conf = PVE::QemuConfig->load_config($vmid); # update/reload\n    }\n\n    # don't regenerate the ISO if the VM is started as part of a live migration\n    # this way we can reuse the old ISO with the correct config\n    if (!$migratedfrom) {\n        if (PVE::QemuServer::Cloudinit::apply_cloudinit_config($conf, $vmid)) {\n            # FIXME: apply_cloudinit_config updates $conf in this case, and it would only drop\n            # $conf->{'special-sections'}->{cloudinit}, so we could just not do this?\n            # But we do it above, so for now let's be consistent.\n            $conf = PVE::QemuConfig->load_config($vmid); # update/reload\n        }\n    }\n\n    # override offline migrated volumes, conf is out of date still\n    if (my $offline_volumes = $migrate_opts->{offline_volumes}) {\n        for my $key (sort keys $offline_volumes->%*) {\n            my $parsed = parse_drive($key, $conf->{$key});\n            $parsed->{file} = $offline_volumes->{$key};\n            $conf->{$key} = print_drive($parsed);\n        }\n    }\n\n    my $defaults = load_defaults();\n\n    # set environment variable useful inside network script\n    # for remote migration the config is available on the target node!\n    if (!$migrate_opts->{remote_node}) {\n        $ENV{PVE_MIGRATED_FROM} = $migratedfrom;\n    }\n\n    PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1);\n\n    my $forcemachine = $params->{forcemachine};\n    my $forcecpu = $params->{forcecpu};\n    my $nets_host_mtu = $params->{'nets-host-mtu'};\n    if ($resume) {\n        # enforce machine and CPU type on suspended vm to ensure HW compatibility\n        $forcemachine = $conf->{runningmachine};\n        $forcecpu = $conf->{runningcpu};\n        $nets_host_mtu = $conf->{'running-nets-host-mtu'};\n        print \"Resuming suspended VM\\n\";\n    }\n\n    my $migration_ip;\n    if (\n        ($statefile && $statefile eq 'tcp' && $migration_type eq 'insecure')\n        || ($migrate_opts->{nbd}\n            && ($nbd_protocol_version == 0 || $migration_type eq 'insecure'))\n    ) {\n        my $nodename = nodename();\n        $migration_ip =\n            PVE::QemuServer::StateFile::get_migration_ip($nodename, $migrate_opts->{network});\n    }\n\n    my $res = {};\n    my $statefile_is_a_volume;\n    my $state_cmdline = [];\n    if ($statefile) {\n        ($state_cmdline, $res->{migrate}, $statefile_is_a_volume) =\n            PVE::QemuServer::StateFile::statefile_cmdline_option(\n                $storecfg, $vmid, $statefile, $migration_type, $migration_ip,\n            );\n    } elsif ($params->{paused}) {\n        $state_cmdline = ['-S'];\n    }\n\n    my $vollist = get_current_vm_volumes($storecfg, $conf);\n    push $vollist->@*, $statefile if $statefile_is_a_volume;\n\n    my ($cmd, $spice_port, $start_timeout);\n    my $pci_reserve_list = [];\n    eval {\n        # With -blockdev, it is necessary to activate the volumes before generating the command line\n        # Plugins can safely deactivate already-active volumes here if needed\n        my $storage_hints = generate_storage_hints($conf, 1);\n        PVE::Storage::activate_volumes($storecfg, $vollist, undef, $storage_hints);\n\n        check_efi_vars($storecfg, $vmid, $conf) if $conf->{bios} && $conf->{bios} eq 'ovmf';\n\n        # Note that for certain cases like templates, the configuration is minimized, so need to ensure\n        # the rest of the function here uses the same configuration that was used to build the command\n        ($cmd, $spice_port, my $pci_devices, $conf) = config_to_command(\n            $storecfg,\n            $vmid,\n            $conf,\n            $defaults,\n            {\n                'force-machine' => $forcemachine,\n                'force-cpu' => $forcecpu,\n                'live-restore-backing' => $params->{'live-restore-backing'},\n                'nets-host-mtu' => $nets_host_mtu,\n            },\n        );\n\n        my $memory = get_current_memory($conf->{memory});\n        $start_timeout = $params->{timeout} // config_aware_timeout($conf, $memory, $resume);\n\n        push $cmd->@*, $state_cmdline->@*;\n\n        for my $device (values $pci_devices->%*) {\n            next if $device->{mdev}; # we don't reserve for mdev devices\n            push $pci_reserve_list->@*, map { $_->{id} } $device->{ids}->@*;\n        }\n\n        # reserve all PCI IDs before actually doing anything with them\n        PVE::QemuServer::PCI::reserve_pci_usage($pci_reserve_list, $vmid, $start_timeout);\n\n        my $uuid;\n        for my $id (sort keys %$pci_devices) {\n            my $d = $pci_devices->{$id};\n            my ($index) = ($id =~ m/^hostpci(\\d+)$/);\n\n            my $chosen_mdev;\n            for my $dev ($d->{ids}->@*) {\n                my $info =\n                    eval { PVE::QemuServer::PCI::prepare_pci_device($vmid, $dev->{id}, $index, $d) };\n                if ($d->{mdev} || $d->{nvidia}) {\n                    warn $@ if $@;\n                    $chosen_mdev = $info;\n                    last if $chosen_mdev; # if successful, we're done\n                } else {\n                    die $@ if $@;\n                }\n            }\n\n            next if !$d->{mdev} && !$d->{nvidia};\n            die \"could not create mediated device\\n\" if !defined($chosen_mdev);\n\n            # nvidia grid needs the uuid of the mdev as qemu parameter\n            if (!defined($uuid) && $chosen_mdev->{vendor} =~ m/^(0x)?10de$/) {\n                if (defined($conf->{smbios1})) {\n                    my $smbios_conf = parse_smbios1($conf->{smbios1});\n                    $uuid = $smbios_conf->{uuid} if defined($smbios_conf->{uuid});\n                }\n                $uuid = PVE::QemuServer::PCI::generate_mdev_uuid($vmid, $index)\n                    if !defined($uuid);\n            }\n        }\n        push @$cmd, '-uuid', $uuid if defined($uuid);\n    };\n    if (my $err = $@) {\n        eval { PVE::Storage::deactivate_volumes($storecfg, $vollist); };\n        warn $@ if $@;\n        eval { cleanup_pci_devices($vmid, $conf) };\n        warn $@ if $@;\n        die $err;\n    }\n\n    my %silence_std_outs = (outfunc => sub { }, errfunc => sub { });\n    eval { # See systemd GH #39141, need to reset failed PartOf units too, or scope might be blocked\n        run_command(\n            ['/bin/systemctl', 'reset-failed', \"pve-dbus-vmstate\\@$vmid.service\"],\n            %silence_std_outs,\n        );\n    };\n    eval { run_command(['/bin/systemctl', 'reset-failed', \"$vmid.scope\"], %silence_std_outs) };\n    eval { run_command(['/bin/systemctl', 'stop', \"$vmid.scope\"], %silence_std_outs) };\n    # Issues with the above 'stop' not being fully completed are extremely rare, a very low\n    # timeout should be more than enough here...\n    PVE::Systemd::wait_for_unit_removed(\"$vmid.scope\", 20);\n\n    my $cpuunits = PVE::CGroup::clamp_cpu_shares($conf->{cpuunits});\n\n    my %run_params = (\n        timeout => $statefile ? undef : $start_timeout,\n        umask => 0077,\n        noerr => 1,\n    );\n\n    # when migrating, prefix QEMU output so other side can pick up any\n    # errors that might occur and show the user\n    if ($migratedfrom) {\n        $run_params{quiet} = 1;\n        $run_params{logfunc} = sub { print \"QEMU: $_[0]\\n\" };\n    } elsif ($cpuinfo->{vendor} eq 'AuthenticAMD' && $cpuinfo->{family} == 26) {\n        $run_params{logfunc} = $log_filter_catch_outdated_zen5_firmware;\n    }\n\n    my %systemd_properties = (\n        Slice => 'qemu.slice',\n        KillMode => 'process',\n        SendSIGKILL => 0,\n        TimeoutStopUSec => ULONG_MAX, # infinity\n        After => ['dbus.service'],\n        # The point is ordering the VMID.scope after these during stop.\n        # During start-up, the slice/scopes are not enabled.\n        Before => ['pve-ha-lrm.service', 'pve-guests.service'],\n    );\n\n    if (PVE::CGroup::cgroup_mode() == 2) {\n        $systemd_properties{CPUWeight} = $cpuunits;\n    } else {\n        $systemd_properties{CPUShares} = $cpuunits;\n    }\n\n    if (my $cpulimit = $conf->{cpulimit}) {\n        $systemd_properties{CPUQuota} = int($cpulimit * 100);\n    }\n    $systemd_properties{timeout} = 10 if $statefile; # setting up the scope should be quick\n\n    my $cleanup_qsd = sub {\n        if (PVE::QemuServer::Helpers::qsd_running_locally($vmid)) {\n            eval { PVE::QemuServer::QSD::quit($vmid); };\n            warn \"stopping QEMU storage daemon failed - $@\" if $@;\n        }\n    };\n\n    my $run_qemu = sub {\n        PVE::Tools::run_fork sub {\n            PVE::Systemd::enter_systemd_scope($vmid, \"Proxmox VE VM $vmid\",\n                %systemd_properties);\n\n            my $virtiofs_sockets = start_all_virtiofsd($conf, $vmid);\n\n            my $tpmpid;\n            if ((my $tpm = $conf->{tpmstate0}) && !PVE::QemuConfig->is_template($conf)) {\n                # start the TPM emulator so QEMU can connect on start\n                eval { $tpmpid = start_swtpm($storecfg, $vmid, $tpm, $migratedfrom); };\n                if (my $err = $@) {\n                    $cleanup_qsd->();\n                    die $err;\n                }\n            }\n\n            my $exitcode = run_command($cmd, %run_params);\n            eval { PVE::QemuServer::Virtiofs::close_sockets(@$virtiofs_sockets); };\n            log_warn(\"closing virtiofs sockets failed - $@\") if $@;\n            if ($exitcode) {\n                if ($tpmpid) {\n                    warn \"stopping swtpm instance (pid $tpmpid) due to QEMU startup error\\n\";\n                    kill 'TERM', $tpmpid;\n                }\n                $cleanup_qsd->();\n\n                die \"QEMU exited with code $exitcode\\n\";\n            }\n        };\n    };\n\n    if ($conf->{hugepages}) {\n\n        my $code = sub {\n            my $hotplug_features =\n                parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');\n            my $hugepages_topology =\n                PVE::QemuServer::Memory::hugepages_topology($conf, $hotplug_features->{memory});\n\n            my $hugepages_host_topology = PVE::QemuServer::Memory::hugepages_host_topology();\n\n            PVE::QemuServer::Memory::hugepages_mount();\n            PVE::QemuServer::Memory::hugepages_allocate(\n                $hugepages_topology, $hugepages_host_topology,\n            );\n\n            eval { $run_qemu->() };\n            if (my $err = $@) {\n                PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology)\n                    if !$conf->{keephugepages};\n                die $err;\n            }\n\n            PVE::QemuServer::Memory::hugepages_pre_deallocate($hugepages_topology)\n                if !$conf->{keephugepages};\n        };\n        eval { PVE::QemuServer::Memory::hugepages_update_locked($code); };\n\n    } else {\n        eval { $run_qemu->() };\n    }\n\n    if (my $err = $@) {\n        # deactivate volumes if start fails\n        eval { PVE::Storage::deactivate_volumes($storecfg, $vollist); };\n        warn $@ if $@;\n        eval { cleanup_pci_devices($vmid, $conf) };\n        warn $@ if $@;\n\n        die \"start failed: $err\";\n    }\n\n    # re-reserve all PCI IDs now that we can know the actual VM PID\n    my $pid = PVE::QemuServer::Helpers::vm_running_locally($vmid);\n    eval { PVE::QemuServer::PCI::reserve_pci_usage($pci_reserve_list, $vmid, undef, $pid) };\n    warn $@ if $@;\n\n    syslog(\"info\", \"VM $vmid started with PID $pid.\");\n\n    if (defined(my $migrate = $res->{migrate})) {\n        if ($migrate->{proto} eq 'tcp') {\n            my $nodename = nodename();\n            my $pfamily = PVE::Tools::get_host_address_family($nodename);\n            $migrate->{port} = PVE::Tools::next_migrate_port($pfamily);\n            $migrate->{uri} = \"tcp:$migrate->{addr}:$migrate->{port}\";\n            mon_cmd($vmid, \"migrate-incoming\", uri => $migrate->{uri});\n        }\n        print \"migration listens on $migrate->{uri}\\n\";\n    } elsif ($statefile) {\n        eval { mon_cmd($vmid, \"cont\"); };\n        warn $@ if $@;\n    }\n\n    #start nbd server for storage migration\n    if (my $nbd = $migrate_opts->{nbd}) {\n\n        my $migrate_storage_uri;\n        # nbd_protocol_version > 0 for unix socket support\n        if (\n            $nbd_protocol_version > 0\n            && ($migration_type eq 'secure' || $migration_type eq 'websocket')\n        ) {\n            my $socket_path = \"/run/qemu-server/$vmid\\_nbd.migrate\";\n            mon_cmd(\n                $vmid,\n                \"nbd-server-start\",\n                addr => { type => 'unix', data => { path => $socket_path } },\n            );\n            $migrate_storage_uri = \"nbd:unix:$socket_path\";\n            $res->{migrate}->{unix_sockets} = [$socket_path];\n        } else {\n            my $nodename = nodename();\n            my $localip = $migration_ip // die \"internal error - no migration IP\";\n            my $pfamily = PVE::Tools::get_host_address_family($nodename);\n            my $storage_migrate_port = PVE::Tools::next_migrate_port($pfamily);\n\n            mon_cmd(\n                $vmid,\n                \"nbd-server-start\",\n                addr => {\n                    type => 'inet',\n                    data => {\n                        host => \"${localip}\",\n                        port => \"${storage_migrate_port}\",\n                    },\n                },\n            );\n            $localip = \"[$localip]\" if Net::IP::ip_is_ipv6($localip);\n            $migrate_storage_uri = \"nbd:${localip}:${storage_migrate_port}\";\n        }\n\n        my $block_info = PVE::QemuServer::Blockdev::get_block_info($vmid);\n\n        foreach my $opt (sort keys %$nbd) {\n            my $drivestr = $nbd->{$opt}->{drivestr};\n            my $volid = $nbd->{$opt}->{volid};\n\n            my $block_node = $block_info->{$opt}->{inserted}->{'node-name'};\n\n            mon_cmd(\n                $vmid,\n                \"block-export-add\",\n                id => \"drive-$opt\",\n                'node-name' => $block_node,\n                writable => JSON::true,\n                type => \"nbd\",\n                name => \"drive-$opt\", # NBD export name\n            );\n\n            my $nbd_uri = \"$migrate_storage_uri:exportname=drive-$opt\";\n            print \"storage migration listens on $nbd_uri volume:$drivestr\\n\";\n            print \"re-using replicated volume: $opt - $volid\\n\"\n                if $nbd->{$opt}->{replicated};\n\n            $res->{drives}->{$opt} = $nbd->{$opt};\n            $res->{drives}->{$opt}->{nbd_uri} = $nbd_uri;\n        }\n    }\n\n    if ($migratedfrom) {\n        eval { PVE::QemuMigrate::Helpers::set_migration_caps($vmid); };\n        warn $@ if $@;\n\n        if ($spice_port) {\n            print \"spice listens on port $spice_port\\n\";\n            $res->{spice_port} = $spice_port;\n            if ($migrate_opts->{spice_ticket}) {\n                mon_cmd(\n                    $vmid, \"set_password\",\n                    protocol => 'spice',\n                    password => $migrate_opts->{spice_ticket},\n                );\n                mon_cmd($vmid, \"expire_password\", protocol => 'spice', time => \"+30\");\n            }\n        }\n\n        # conntrack migration is only supported for intra-cluster migrations\n        if ($migrate_opts->{with_conntrack_state} && !$migrate_opts->{remote_node}) {\n            PVE::QemuServer::DBusVMState::qemu_add_dbus_vmstate($vmid);\n        }\n    } else {\n        mon_cmd($vmid, \"balloon\", value => $conf->{balloon} * 1024 * 1024)\n            if !$statefile && $conf->{balloon};\n\n        foreach my $opt (keys %$conf) {\n            next if $opt !~ m/^net\\d+$/;\n            my $nicconf = PVE::QemuServer::Network::parse_net($conf->{$opt});\n            qemu_set_link_status($vmid, $opt, 0) if $nicconf->{link_down};\n        }\n        PVE::QemuServer::Network::add_nets_bridge_fdb($conf, $vmid);\n    }\n\n    if (!defined($conf->{balloon}) || $conf->{balloon}) {\n        eval {\n            mon_cmd(\n                $vmid,\n                'qom-set',\n                path => \"machine/peripheral/balloon0\",\n                property => \"guest-stats-polling-interval\",\n                value => 2,\n            );\n        };\n        log_warn(\"could not set polling interval for ballooning - $@\") if $@;\n    }\n\n    if ($resume) {\n        print \"Resumed VM, removing state\\n\";\n        if (my $vmstate = $conf->{vmstate}) {\n            PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);\n            PVE::Storage::vdisk_free($storecfg, $vmstate);\n        }\n        delete $conf->@{qw(lock vmstate running-nets-host-mtu runningmachine runningcpu)};\n        PVE::QemuConfig->write_config($vmid, $conf);\n    } elsif (!$conf->{vmstate} && !$migratedfrom) {\n        remove_left_over_vmstate_opts($vmid, $conf);\n    }\n\n    PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');\n\n    my ($current_machine, $is_deprecated) =\n        PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n    if ($is_deprecated) {\n        log_warn(\n            \"current machine version '$current_machine' is deprecated - see the documentation and \"\n                . \"change to a newer one\",\n        );\n    }\n\n    return $res;\n}\n\nsub vm_commandline {\n    my ($storecfg, $vmid, $snapname) = @_;\n\n    my $conf = PVE::QemuConfig->load_config($vmid);\n\n    my $options = { 'dry-run' => 1 };\n    if ($snapname) {\n        my $snapshot = $conf->{snapshots}->{$snapname};\n        die \"snapshot '$snapname' does not exist\\n\" if !defined($snapshot);\n\n        # check for machine or CPU overrides in snapshot\n        $options->{'force-machine'} = $snapshot->{runningmachine};\n        $options->{'force-cpu'} = $snapshot->{runningcpu};\n        $options->{'nets-host-mtu'} = $snapshot->{'running-nets-host-mtu'};\n\n        $snapshot->{digest} = $conf->{digest}; # keep file digest for API\n\n        $conf = $snapshot;\n    }\n\n    my $defaults = load_defaults();\n\n    my $running = PVE::QemuServer::Helpers::vm_running_locally($vmid);\n    my $volumes = [];\n\n    # With -blockdev, it is necessary to activate the volumes before generating the command line\n    if (!$running) {\n        $volumes = get_current_vm_volumes($storecfg, $conf);\n        PVE::Storage::activate_volumes($storecfg, $volumes);\n    }\n\n    # There might be concurrent operations on the volumes, so do not deactivate.\n\n    my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults, $options);\n\n    return PVE::Tools::cmd2string($cmd);\n}\n\nsub vm_reset {\n    my ($vmid, $skiplock) = @_;\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            PVE::QemuConfig->check_lock($conf) if !$skiplock;\n\n            mon_cmd($vmid, \"system_reset\");\n        },\n    );\n}\n\nsub get_vm_volumes {\n    my ($conf) = @_;\n\n    my $vollist = [];\n    foreach_volid(\n        $conf,\n        sub {\n            my ($volid, $attr) = @_;\n\n            return if $volid =~ m|^/|;\n\n            my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n            return if !$sid;\n\n            push @$vollist, $volid;\n        },\n    );\n\n    return $vollist;\n}\n\n# Get volumes defined in the current VM configuration, including the VM state file.\nsub get_current_vm_volumes {\n    my ($storecfg, $conf) = @_;\n\n    my $volumes = [];\n\n    PVE::QemuConfig->foreach_volume_full(\n        $conf,\n        { extra_keys => ['vmstate'] },\n        sub {\n            my ($ds, $drive) = @_;\n\n            if (PVE::Storage::parse_volume_id($drive->{file}, 1)) {\n                check_volume_storage_type($storecfg, $drive->{file});\n                push $volumes->@*, $drive->{file};\n            }\n        },\n    );\n\n    return $volumes;\n}\n\nsub cleanup_pci_devices {\n    my ($vmid, $conf) = @_;\n\n    # templates don't use pci devices\n    return if $conf->{template};\n\n    my $reservations = PVE::QemuServer::PCI::get_reservations($vmid);\n    # clean up nvidia devices\n    for my $id ($reservations->@*) {\n        $id = PVE::SysFSTools::normalize_pci_id($id);\n\n        my $create_path = \"/sys/bus/pci/devices/$id/nvidia/current_vgpu_type\";\n\n        next if !-f $create_path;\n\n        for (my $i = 0; $i < 10; $i++) {\n            last if file_read_firstline($create_path) eq \"0\";\n            sleep 1;\n            PVE::SysFSTools::file_write($create_path, \"0\");\n        }\n        if (file_read_firstline($create_path) ne \"0\") {\n            warn \"could not cleanup nvidia vgpu for '$id'\\n\";\n        }\n    }\n\n    foreach my $key (keys %$conf) {\n        next if $key !~ m/^hostpci(\\d+)$/;\n        my $hostpciindex = $1;\n        my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $hostpciindex);\n        my $d = parse_hostpci($conf->{$key});\n        if ($d->{mdev}) {\n            # NOTE: avoid PVE::SysFSTools::pci_cleanup_mdev_device as it requires PCI ID and we\n            # don't want to break ABI just for this two liner\n            my $dev_sysfs_dir = \"/sys/bus/mdev/devices/$uuid\";\n\n            # some nvidia vgpu driver versions want to clean the mdevs up themselves, and error\n            # out when we do it first. so wait for up to 10 seconds and then try it manually\n            if ($d->{ids}->[0]->[0]->{vendor} =~ m/^(0x)?10de$/ && -e $dev_sysfs_dir) {\n                my $count = 0;\n                while (-e $dev_sysfs_dir && $count < 10) {\n                    sleep 1;\n                    $count++;\n                }\n                print \"waited $count seconds for mediated device driver finishing clean up\\n\";\n            }\n\n            if (-e $dev_sysfs_dir) {\n                print \"actively clean up mediated device with UUID $uuid\\n\";\n                PVE::SysFSTools::file_write(\"$dev_sysfs_dir/remove\", \"1\");\n            }\n        }\n    }\n    PVE::QemuServer::PCI::remove_pci_reservation($vmid);\n}\n\nsub vm_stop_cleanup {\n    my ($storecfg, $vmid, $conf, $keepActive, $apply_pending_changes, $noerr) = @_;\n\n    eval {\n        PVE::QemuServer::QSD::quit($vmid)\n            if PVE::QemuServer::Helpers::qsd_running_locally($vmid);\n\n        # ensure that no dbus-vmstate helper is left running in any case\n        # at this point, it should never be still running, so quiesce any warnings\n        PVE::QemuServer::DBusVMState::qemu_del_dbus_vmstate($vmid, quiet => 1);\n\n        if (!$keepActive) {\n            my $vollist = get_vm_volumes($conf);\n            PVE::Storage::deactivate_volumes($storecfg, $vollist);\n        }\n\n        foreach my $ext (qw(mon qmp pid vnc qga)) {\n            unlink \"/var/run/qemu-server/${vmid}.$ext\";\n        }\n\n        if ($conf->{ivshmem}) {\n            my $ivshmem = parse_property_string($ivshmem_fmt, $conf->{ivshmem});\n            # just delete it for now, VMs which have this already open do not\n            # are affected, but new VMs will get a separated one. If this\n            # becomes an issue we either add some sort of ref-counting or just\n            # add a \"don't delete on stop\" flag to the ivshmem format.\n            unlink '/dev/shm/pve-shm-' . ($ivshmem->{name} // $vmid);\n        }\n\n        cleanup_pci_devices($vmid, $conf);\n\n        vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes;\n    };\n    if (my $err = $@) {\n        die $err if !$noerr;\n        warn $err;\n    }\n}\n\n# call only in locked context\nsub _do_vm_stop {\n    my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive) = @_;\n\n    my $pid = check_running($vmid, $nocheck);\n    return if !$pid;\n\n    my $conf;\n    if (!$nocheck) {\n        $conf = PVE::QemuConfig->load_config($vmid);\n        PVE::QemuConfig->check_lock($conf) if !$skiplock;\n        if (!defined($timeout) && $shutdown && $conf->{startup}) {\n            my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});\n            $timeout = $opts->{down} if $opts->{down};\n        }\n        PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');\n    }\n\n    eval {\n        if ($shutdown) {\n            if (defined($conf) && get_qga_key($conf, 'enabled') && qga_check_running($vmid)) {\n                mon_cmd($vmid, \"guest-shutdown\", timeout => $timeout);\n            } else {\n                mon_cmd($vmid, \"system_powerdown\");\n            }\n        } else {\n            mon_cmd($vmid, \"quit\");\n        }\n    };\n    my $err = $@;\n\n    if (!$err) {\n        $timeout = 60 if !defined($timeout);\n\n        my $count = 0;\n        while (($count < $timeout) && check_running($vmid, $nocheck)) {\n            $count++;\n            sleep 1;\n        }\n\n        if ($count >= $timeout) {\n            if ($force) {\n                warn \"VM still running - terminating now with SIGTERM\\n\";\n                kill 15, $pid;\n            } else {\n                die \"VM quit/powerdown failed - got timeout\\n\";\n            }\n        } else {\n            vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1, 1) if $conf;\n            return;\n        }\n    } else {\n        if (!check_running($vmid, $nocheck)) {\n            warn \"Unexpected: VM shutdown command failed, but VM not running anymore..\\n\";\n            return;\n        }\n        if ($force) {\n            warn \"VM quit/powerdown failed - terminating now with SIGTERM\\n\";\n            kill 15, $pid;\n        } else {\n            die \"VM quit/powerdown failed\\n\";\n        }\n    }\n\n    # wait again\n    $timeout = 10;\n\n    my $count = 0;\n    while (($count < $timeout) && check_running($vmid, $nocheck)) {\n        $count++;\n        sleep 1;\n    }\n\n    if ($count >= $timeout) {\n        warn \"VM still running - terminating now with SIGKILL\\n\";\n        kill 9, $pid;\n        sleep 1;\n    }\n\n    vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1, 1) if $conf;\n}\n\n# Note: use $nocheck to skip tests if VM configuration file exists.\n# We need that when migration VMs to other nodes (files already moved)\n# Note: we set $keepActive in vzdump stop mode - volumes need to stay active\nsub vm_stop {\n    my (\n        $storecfg,\n        $vmid,\n        $skiplock,\n        $nocheck,\n        $timeout,\n        $shutdown,\n        $force,\n        $keepActive,\n        $migratedfrom,\n    ) = @_;\n\n    $force = 1 if !defined($force) && !$shutdown;\n\n    if ($migratedfrom) {\n        my $pid = check_running($vmid, $nocheck, $migratedfrom);\n        kill 15, $pid if $pid;\n        my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);\n        vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 0, 1);\n        return;\n    }\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n            _do_vm_stop(\n                $storecfg,\n                $vmid,\n                $skiplock,\n                $nocheck,\n                $timeout,\n                $shutdown,\n                $force,\n                $keepActive,\n            );\n        },\n    );\n}\n\nsub vm_reboot {\n    my ($vmid, $timeout) = @_;\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n            eval {\n\n                # only reboot if running, as qmeventd starts it again on a stop event\n                return if !check_running($vmid);\n\n                create_reboot_request($vmid);\n\n                my $storecfg = PVE::Storage::config();\n                _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1);\n\n            };\n            if (my $err = $@) {\n                # avoid that the next normal shutdown will be confused for a reboot\n                clear_reboot_request($vmid);\n                die $err;\n            }\n        },\n    );\n}\n\nsub vm_sendkey {\n    my ($vmid, $skiplock, $key) = @_;\n\n    PVE::QemuConfig->lock_config(\n        $vmid,\n        sub {\n\n            my $conf = PVE::QemuConfig->load_config($vmid);\n\n            # there is no qmp command, so we use the human monitor command\n            my $res = PVE::QemuServer::Monitor::hmp_cmd($vmid, \"sendkey $key\");\n            die $res if $res ne '';\n        },\n    );\n}\n\nsub check_bridge_access {\n    my ($rpcenv, $authuser, $conf) = @_;\n\n    return 1 if $authuser eq 'root@pam';\n\n    for my $opt (sort keys $conf->%*) {\n        next if $opt !~ m/^net\\d+$/;\n        my $net = PVE::QemuServer::Network::parse_net($conf->{$opt});\n        my ($bridge, $tag, $trunks) = $net->@{ 'bridge', 'tag', 'trunks' };\n        next if !defined($bridge); # no vnet to check for\n        PVE::GuestHelpers::check_vnet_access($rpcenv, $authuser, $bridge, $tag, $trunks);\n    }\n    return 1;\n}\n\nsub check_mapping_access {\n    my ($rpcenv, $user, $conf) = @_;\n\n    return 1 if $user eq 'root@pam';\n\n    for my $opt (keys $conf->%*) {\n        if ($opt =~ m/^usb\\d+$/) {\n            my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $conf->{$opt});\n            if (my $host = $device->{host}) {\n                die \"only root can set '$opt' config for real devices\\n\"\n                    if $host !~ m/^spice$/i;\n            } elsif ($device->{mapping}) {\n                $rpcenv->check_full($user, \"/mapping/usb/$device->{mapping}\", ['Mapping.Use']);\n            } else {\n                die \"either 'host' or 'mapping' must be set.\\n\";\n            }\n        } elsif ($opt =~ m/^hostpci\\d+$/) {\n            my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $conf->{$opt});\n            if ($device->{host}) {\n                die \"only root can set '$opt' config for non-mapped devices\\n\";\n            } elsif ($device->{mapping}) {\n                $rpcenv->check_full($user, \"/mapping/pci/$device->{mapping}\", ['Mapping.Use']);\n            } else {\n                die \"either 'host' or 'mapping' must be set.\\n\";\n            }\n        } elsif ($opt =~ m/^rng\\d+$/) {\n            my $device = PVE::JSONSchema::parse_property_string('pve-qm-rng', $conf->{$opt});\n\n            if ($device->{source} && $device->{source} eq '/dev/hwrng') {\n                $rpcenv->check_full($user, \"/mapping/hwrng\", ['Mapping.Use']);\n            }\n        } elsif ($opt =~ m/^virtiofs\\d$/) {\n            my $virtiofs = PVE::JSONSchema::parse_property_string('pve-qm-virtiofs', $conf->{$opt});\n            $rpcenv->check_full($user, \"/mapping/dir/$virtiofs->{dirid}\", ['Mapping.Use']);\n        }\n    }\n}\n\nsub check_restore_permissions {\n    my ($rpcenv, $user, $conf) = @_;\n\n    check_bridge_access($rpcenv, $user, $conf);\n    check_mapping_access($rpcenv, $user, $conf);\n}\n# vzdump restore implementation\n\nsub tar_archive_read_firstfile {\n    my $archive = shift;\n\n    die \"ERROR: file '$archive' does not exist\\n\" if !-f $archive;\n\n    # try to detect archive type first\n    my $pid = open(my $fh, '-|', 'tar', 'tf', $archive)\n        || die \"unable to open file '$archive'\\n\";\n    my $firstfile = <$fh>;\n    kill 15, $pid;\n    close $fh;\n\n    die \"ERROR: archive contaions no data\\n\" if !$firstfile;\n    chomp $firstfile;\n\n    return $firstfile;\n}\n\nsub tar_restore_cleanup {\n    my ($storecfg, $statfile) = @_;\n\n    print STDERR \"starting cleanup\\n\";\n\n    if (my $fd = IO::File->new($statfile, \"r\")) {\n        while (defined(my $line = <$fd>)) {\n            if ($line =~ m/vzdump:([^\\s:]*):(\\S+)$/) {\n                my $volid = $2;\n                eval {\n                    if ($volid =~ m|^/|) {\n                        unlink $volid || die 'unlink failed\\n';\n                    } else {\n                        PVE::Storage::vdisk_free($storecfg, $volid);\n                    }\n                    print STDERR \"temporary volume '$volid' successfully removed\\n\";\n                };\n                print STDERR \"unable to cleanup '$volid' - $@\" if $@;\n            } else {\n                print STDERR \"unable to parse line in statfile - $line\";\n            }\n        }\n        $fd->close();\n    }\n}\n\nsub restore_file_archive {\n    my ($archive, $vmid, $user, $opts) = @_;\n\n    return restore_vma_archive($archive, $vmid, $user, $opts)\n        if $archive eq '-';\n\n    my $info = PVE::Storage::archive_info($archive);\n    my $format = $opts->{format} // $info->{format};\n    my $comp = $info->{compression};\n\n    # try to detect archive format\n    if ($format eq 'tar') {\n        return restore_tar_archive($archive, $vmid, $user, $opts);\n    } else {\n        return restore_vma_archive($archive, $vmid, $user, $opts, $comp);\n    }\n}\n\n# helper to remove disks that will not be used after restore\nmy $restore_cleanup_oldconf = sub {\n    my ($storecfg, $vmid, $oldconf, $virtdev_hash) = @_;\n\n    my $kept_disks = {};\n\n    PVE::QemuConfig->foreach_volume(\n        $oldconf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            return if drive_is_cdrom($drive, 1);\n\n            my $volid = $drive->{file};\n            return if !$volid || $volid =~ m|^/|;\n\n            my ($path, $owner) = PVE::Storage::path($storecfg, $volid);\n            return if !$path || !$owner || ($owner != $vmid);\n\n            # Note: only delete disk we want to restore\n            # other volumes will become unused\n            if ($virtdev_hash->{$ds}) {\n                eval { PVE::Storage::vdisk_free($storecfg, $volid); };\n                if (my $err = $@) {\n                    warn $err;\n                }\n            } else {\n                $kept_disks->{$volid} = 1;\n            }\n        },\n    );\n\n    # after the restore we have no snapshots anymore\n    for my $snapname (keys $oldconf->{snapshots}->%*) {\n        my $snap = $oldconf->{snapshots}->{$snapname};\n        if ($snap->{vmstate}) {\n            eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); };\n            if (my $err = $@) {\n                warn $err;\n            }\n        }\n\n        for my $volid (keys $kept_disks->%*) {\n            eval { PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname); };\n            warn $@ if $@;\n        }\n    }\n};\n\n# Helper to parse vzdump backup device hints\n#\n# $rpcenv: Environment, used to ckeck storage permissions\n# $user: User ID, to check storage permissions\n# $storecfg: Storage configuration\n# $fh: the file handle for reading the configuration\n# $devinfo: should contain device sizes for all backu-up'ed devices\n# $options: backup options (pool, default storage)\n#\n# Return: $virtdev_hash, updates $devinfo (add devname, virtdev, format, storeid)\nmy $parse_backup_hints = sub {\n    my ($rpcenv, $user, $storecfg, $fh, $devinfo, $options) = @_;\n\n    my $check_storage = sub { # assert if an image can be allocate\n        my ($storeid, $scfg) = @_;\n        die \"Content type 'images' is not available on storage '$storeid'\\n\"\n            if !$scfg->{content}->{images};\n        $rpcenv->check($user, \"/storage/$storeid\", ['Datastore.AllocateSpace'])\n            if $user ne 'root@pam';\n    };\n\n    my $virtdev_hash = {};\n    while (defined(my $line = <$fh>)) {\n        if ($line =~ m/^\\#qmdump\\#map:(\\S+):(\\S+):(\\S*):(\\S*):$/) {\n            my ($virtdev, $devname, $storeid, $format) = ($1, $2, $3, $4);\n            die \"archive does not contain data for drive '$virtdev'\\n\"\n                if !$devinfo->{$devname};\n\n            if (defined($options->{storage})) {\n                $storeid = $options->{storage} || 'local';\n            } elsif (!$storeid) {\n                $storeid = 'local';\n            }\n            $format = 'raw' if !$format;\n            $devinfo->{$devname}->{devname} = $devname;\n            $devinfo->{$devname}->{virtdev} = $virtdev;\n            $devinfo->{$devname}->{format} = $format;\n            $devinfo->{$devname}->{storeid} = $storeid;\n\n            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n            $check_storage->($storeid, $scfg); # permission and content type check\n\n            $virtdev_hash->{$virtdev} = $devinfo->{$devname};\n        } elsif ($line =~ m/^((?:ide|sata|scsi)\\d+):\\s*(.*)\\s*$/) {\n            my $virtdev = $1;\n            my $drive = parse_drive($virtdev, $2);\n\n            if (drive_is_cloudinit($drive)) {\n                my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});\n                $storeid = $options->{storage} if defined($options->{storage});\n                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n                my $format = eval { checked_volume_format($storecfg, $drive->{file}) } // 'raw';\n\n                $check_storage->($storeid, $scfg); # permission and content type check\n\n                $virtdev_hash->{$virtdev} = {\n                    format => $format,\n                    storeid => $storeid,\n                    size => PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE,\n                    is_cloudinit => 1,\n                };\n            }\n        }\n    }\n\n    return $virtdev_hash;\n};\n\n# Helper to allocate and activate all volumes required for a restore\n#\n# $storecfg: Storage configuration\n# $virtdev_hash: as returned by parse_backup_hints()\n#\n# Returns: { $virtdev => $volid }\nmy $restore_allocate_devices = sub {\n    my ($storecfg, $virtdev_hash, $vmid) = @_;\n\n    my $map = {};\n    foreach my $virtdev (sort keys %$virtdev_hash) {\n        my $d = $virtdev_hash->{$virtdev};\n        die \"got no size for '$virtdev'\\n\" if !defined($d->{size});\n        my $alloc_size = int(($d->{size} + 1024 - 1) / 1024);\n        my $storeid = $d->{storeid};\n        my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n\n        # falls back to default format if requested format is not supported\n        $d->{format} = PVE::Storage::resolve_format_hint($storecfg, $storeid, $d->{format});\n\n        my $name;\n        if ($d->{is_cloudinit}) {\n            $name = \"vm-$vmid-cloudinit\";\n            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n            if ($scfg->{path}) {\n                $name .= \".$d->{format}\";\n            }\n        }\n\n        my $volid =\n            PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $d->{format}, $name, $alloc_size);\n\n        print STDERR \"new volume ID is '$volid'\\n\";\n        $d->{volid} = $volid;\n\n        PVE::Storage::activate_volumes($storecfg, [$volid]);\n\n        $map->{$virtdev} = $volid;\n    }\n\n    return $map;\n};\n\nsub restore_update_config_line {\n    my ($cookie, $map, $line, $unique) = @_;\n\n    return '' if $line =~ m/^\\#qmdump\\#/;\n    return '' if $line =~ m/^\\#vzdump\\#/;\n    return '' if $line =~ m/^lock:/;\n    return '' if $line =~ m/^unused\\d+:/;\n    return '' if $line =~ m/^parent:/;\n\n    my $res = '';\n\n    my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');\n    if (($line =~ m/^(vlan(\\d+)):\\s*(\\S+)\\s*$/)) {\n        # try to convert old 1.X settings\n        my ($id, $ind, $ethcfg) = ($1, $2, $3);\n        foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {\n            my ($model, $macaddr) = split(/\\=/, $devconfig);\n            $macaddr = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if !$macaddr || $unique;\n            my $net = {\n                model => $model,\n                bridge => \"vmbr$ind\",\n                macaddr => $macaddr,\n            };\n            my $netstr = PVE::QemuServer::Network::print_net($net);\n\n            $res .= \"net$cookie->{netcount}: $netstr\\n\";\n            $cookie->{netcount}++;\n        }\n    } elsif (($line =~ m/^(net\\d+):\\s*(\\S+)\\s*$/) && $unique) {\n        my ($id, $netstr) = ($1, $2);\n        my $net = PVE::QemuServer::Network::parse_net($netstr);\n        $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if $net->{macaddr};\n        $netstr = PVE::QemuServer::Network::print_net($net);\n        $res .= \"$id: $netstr\\n\";\n    } elsif ($line =~ m/^((ide|scsi|virtio|sata|efidisk|tpmstate)\\d+):\\s*(\\S+)\\s*$/) {\n        my $virtdev = $1;\n        my $value = $3;\n        my $di = parse_drive($virtdev, $value);\n        if (defined($di->{backup}) && !$di->{backup}) {\n            $res .= \"#$line\";\n        } elsif ($map->{$virtdev}) {\n            delete $di->{format}; # format can change on restore\n            $di->{file} = $map->{$virtdev};\n            $value = print_drive($di);\n            $res .= \"$virtdev: $value\\n\";\n        } else {\n            $res .= $line;\n        }\n    } elsif (($line =~ m/^vmgenid: (.*)/)) {\n        my $vmgenid = $1;\n        if ($vmgenid ne '0') {\n            # always generate a new vmgenid if there was a valid one setup\n            $vmgenid = generate_uuid();\n        }\n        $res .= \"vmgenid: $vmgenid\\n\";\n    } elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) {\n        my ($uuid, $uuid_str);\n        UUID::generate($uuid);\n        UUID::unparse($uuid, $uuid_str);\n        my $smbios1 = parse_smbios1($2);\n        $smbios1->{uuid} = $uuid_str;\n        $res .= $1 . print_smbios1($smbios1) . \"\\n\";\n    } else {\n        $res .= $line;\n    }\n\n    return $res;\n}\n\nmy $restore_deactivate_volumes = sub {\n    my ($storecfg, $virtdev_hash) = @_;\n\n    my $vollist = [];\n    for my $dev (values $virtdev_hash->%*) {\n        push $vollist->@*, $dev->{volid} if $dev->{volid};\n    }\n\n    eval { PVE::Storage::deactivate_volumes($storecfg, $vollist); };\n    print STDERR $@ if $@;\n};\n\nmy $restore_destroy_volumes = sub {\n    my ($storecfg, $virtdev_hash) = @_;\n\n    for my $dev (values $virtdev_hash->%*) {\n        my $volid = $dev->{volid} or next;\n        eval {\n            PVE::Storage::vdisk_free($storecfg, $volid);\n            print STDERR \"temporary volume '$volid' successfully removed\\n\";\n        };\n        print STDERR \"unable to cleanup '$volid' - $@\" if $@;\n    }\n};\n\nsub restore_merge_config {\n    my ($filename, $backup_conf_raw, $override_conf) = @_;\n\n    my $backup_conf = parse_vm_config($filename, $backup_conf_raw);\n    for my $key (keys $override_conf->%*) {\n        $backup_conf->{$key} = $override_conf->{$key};\n    }\n\n    return $backup_conf;\n}\n\nsub scan_volids {\n    my ($cfg, $vmid) = @_;\n\n    my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid, undef, 'images');\n\n    my $volid_hash = {};\n    foreach my $storeid (keys %$info) {\n        foreach my $item (@{ $info->{$storeid} }) {\n            next if !($item->{volid} && $item->{size});\n            $item->{path} = PVE::Storage::path($cfg, $item->{volid});\n            $volid_hash->{ $item->{volid} } = $item;\n        }\n    }\n\n    return $volid_hash;\n}\n\nsub update_disk_config {\n    my ($vmid, $conf, $volid_hash) = @_;\n\n    my $changes;\n    my $prefix = \"VM $vmid\";\n\n    # used and unused disks\n    my $referenced = {};\n\n    # Note: it is allowed to define multiple storages with same path (alias), so\n    # we need to check both 'volid' and real 'path' (two different volid can point\n    # to the same path).\n\n    my $referencedpath = {};\n\n    # update size info\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($opt, $drive) = @_;\n\n            my $volid = $drive->{file};\n            return if !$volid;\n            my $volume = $volid_hash->{$volid};\n\n            # mark volid as \"in-use\" for next step\n            $referenced->{$volid} = 1;\n            if ($volume && (my $path = $volume->{path})) {\n                $referencedpath->{$path} = 1;\n            }\n\n            return if drive_is_cdrom($drive);\n            return if !$volume;\n\n            my ($updated, $msg) =\n                PVE::QemuServer::Drive::update_disksize($drive, $volume->{size});\n            if (defined($updated)) {\n                $changes = 1;\n                $conf->{$opt} = print_drive($updated);\n                print \"$prefix ($opt): $msg\\n\";\n            }\n        },\n    );\n\n    # remove 'unusedX' entry if volume is used\n    PVE::QemuConfig->foreach_unused_volume(\n        $conf,\n        sub {\n            my ($opt, $drive) = @_;\n\n            my $volid = $drive->{file};\n            return if !$volid;\n\n            my $path;\n            $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};\n            if ($referenced->{$volid} || ($path && $referencedpath->{$path})) {\n                print \"$prefix remove entry '$opt', its volume '$volid' is in use\\n\";\n                $changes = 1;\n                delete $conf->{$opt};\n            }\n\n            $referenced->{$volid} = 1;\n            $referencedpath->{$path} = 1 if $path;\n        },\n    );\n\n    if (my $fleecing = $conf->{'special-sections'}->{fleecing}) {\n        $referenced->{$_} = 1 for PVE::Tools::split_list($fleecing->{'fleecing-images'});\n    }\n\n    foreach my $volid (sort keys %$volid_hash) {\n        next if $volid =~ m/vm-$vmid-state-/;\n        next if $referenced->{$volid};\n        my $path = $volid_hash->{$volid}->{path};\n        next if !$path; # just to be sure\n        next if $referencedpath->{$path};\n        $changes = 1;\n        my $key = PVE::QemuConfig->add_unused_volume($conf, $volid);\n        print \"$prefix add unreferenced volume '$volid' as '$key' to config\\n\";\n        $referencedpath->{$path} = 1; # avoid to add more than once (aliases)\n    }\n\n    return $changes;\n}\n\nsub rescan {\n    my ($vmid, $nolock, $dryrun) = @_;\n\n    my $cfg = PVE::Storage::config();\n\n    print \"rescan volumes...\\n\";\n    my $volid_hash = scan_volids($cfg, $vmid);\n\n    my $updatefn = sub {\n        my ($vmid) = @_;\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n\n        PVE::QemuConfig->check_lock($conf);\n\n        my $vm_volids = {};\n        foreach my $volid (keys %$volid_hash) {\n            my $info = $volid_hash->{$volid};\n            $vm_volids->{$volid} = $info if $info->{vmid} && $info->{vmid} == $vmid;\n        }\n\n        my $changes = update_disk_config($vmid, $conf, $vm_volids);\n\n        PVE::QemuConfig->write_config($vmid, $conf) if $changes && !$dryrun;\n    };\n\n    if (defined($vmid)) {\n        if ($nolock) {\n            &$updatefn($vmid);\n        } else {\n            PVE::QemuConfig->lock_config($vmid, $updatefn, $vmid);\n        }\n    } else {\n        my $vmlist = config_list();\n        foreach my $vmid (keys %$vmlist) {\n            if ($nolock) {\n                &$updatefn($vmid);\n            } else {\n                PVE::QemuConfig->lock_config($vmid, $updatefn, $vmid);\n            }\n        }\n    }\n}\n\nsub restore_proxmox_backup_archive {\n    my ($archive, $vmid, $user, $options) = @_;\n\n    my $storecfg = PVE::Storage::config();\n\n    my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive);\n    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n\n    my $fingerprint = $scfg->{fingerprint};\n    my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);\n\n    my $repo = PVE::PBSClient::get_repository($scfg);\n    my $namespace = $scfg->{namespace};\n\n    # This is only used for `pbs-restore` and the QEMU PBS driver (live-restore)\n    my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);\n    local $ENV{PBS_PASSWORD} = $password;\n    local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);\n\n    my ($vtype, $pbs_backup_name, undef, undef, undef, undef, $format) =\n        PVE::Storage::parse_volname($storecfg, $archive);\n\n    die \"got unexpected vtype '$vtype'\\n\" if $vtype ne 'backup';\n\n    die \"got unexpected backup format '$format'\\n\" if $format ne 'pbs-vm';\n\n    my $tmpdir = \"/var/tmp/vzdumptmp$$\";\n    rmtree $tmpdir;\n    mkpath $tmpdir;\n\n    my $conffile = PVE::QemuConfig->config_file($vmid);\n    # disable interrupts (always do cleanups)\n    local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n        sub { print STDERR \"got interrupt - ignored\\n\"; };\n\n    # Note: $oldconf is undef if VM does not exists\n    my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);\n    my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);\n    my $new_conf_raw = '';\n\n    my $rpcenv = PVE::RPCEnvironment::get();\n    my $devinfo = {}; # info about drives included in backup\n    my $virtdev_hash = {}; # info about allocated drives\n\n    eval {\n        # enable interrupts\n        local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n            local $SIG{PIPE} = sub { die \"interrupted by signal\\n\"; };\n\n        my $cfgfn = \"$tmpdir/qemu-server.conf\";\n        my $firewall_config_fn = \"$tmpdir/fw.conf\";\n        my $index_fn = \"$tmpdir/index.json\";\n\n        my $cmd = \"restore\";\n\n        my $param = [$pbs_backup_name, \"index.json\", $index_fn];\n        PVE::Storage::PBSPlugin::run_raw_client_cmd($scfg, $storeid, $cmd, $param);\n        my $index = PVE::Tools::file_get_contents($index_fn);\n        $index = decode_json($index);\n\n        foreach my $info (@{ $index->{files} }) {\n            if ($info->{filename} =~ m/^(drive-\\S+).img.fidx$/) {\n                my $devname = $1;\n                if ($info->{size} =~ m/^(\\d+)$/) { # untaint size\n                    $devinfo->{$devname}->{size} = $1;\n                } else {\n                    die \"unable to parse file size in 'index.json' - got '$info->{size}'\\n\";\n                }\n            }\n        }\n\n        my $is_qemu_server_backup =\n            scalar(grep { $_->{filename} eq 'qemu-server.conf.blob' } @{ $index->{files} });\n        if (!$is_qemu_server_backup) {\n            die\n                \"backup does not look like a qemu-server backup (missing 'qemu-server.conf' file)\\n\";\n        }\n        my $has_firewall_config =\n            scalar(grep { $_->{filename} eq 'fw.conf.blob' } @{ $index->{files} });\n\n        $param = [$pbs_backup_name, \"qemu-server.conf\", $cfgfn];\n        PVE::Storage::PBSPlugin::run_raw_client_cmd($scfg, $storeid, $cmd, $param);\n\n        if ($has_firewall_config) {\n            $param = [$pbs_backup_name, \"fw.conf\", $firewall_config_fn];\n            PVE::Storage::PBSPlugin::run_raw_client_cmd($scfg, $storeid, $cmd, $param);\n\n            my $pve_firewall_dir = '/etc/pve/firewall';\n            mkdir $pve_firewall_dir; # make sure the dir exists\n            PVE::Tools::file_copy($firewall_config_fn, \"${pve_firewall_dir}/$vmid.fw\");\n        }\n\n        my $fh = IO::File->new($cfgfn, \"r\")\n            || die \"unable to read qemu-server.conf - $!\\n\";\n\n        $virtdev_hash =\n            $parse_backup_hints->($rpcenv, $user, $storecfg, $fh, $devinfo, $options);\n\n        # fixme: rate limit?\n\n        # create empty/temp config\n        PVE::Tools::file_set_contents($conffile, \"memory: 128\\nlock: create\");\n\n        $restore_cleanup_oldconf->($storecfg, $vmid, $oldconf, $virtdev_hash) if $oldconf;\n\n        # allocate volumes\n        my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid);\n\n        foreach my $virtdev (sort keys %$virtdev_hash) {\n            my $d = $virtdev_hash->{$virtdev};\n            next if $d->{is_cloudinit}; # no need to restore cloudinit\n\n            # this fails if storage is unavailable\n            my $volid = $d->{volid};\n            my $path = PVE::Storage::path($storecfg, $volid);\n\n            # for live-restore we only want to preload the efidisk and TPM state\n            next if $options->{live} && $virtdev ne 'efidisk0' && $virtdev ne 'tpmstate0';\n\n            my @ns_arg;\n            if (defined(my $ns = $scfg->{namespace})) {\n                @ns_arg = ('--ns', $ns);\n            }\n\n            my $pbs_restore_cmd = [\n                '/usr/bin/pbs-restore',\n                '--repository',\n                $repo,\n                @ns_arg,\n                $pbs_backup_name,\n                \"$d->{devname}.img.fidx\",\n                $path,\n                '--verbose',\n            ];\n\n            push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};\n            push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;\n\n            if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {\n                push @$pbs_restore_cmd, '--skip-zero';\n            }\n\n            my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);\n            print \"restore proxmox backup image: $dbg_cmdstring\\n\";\n            run_command($pbs_restore_cmd);\n        }\n\n        $fh->seek(0, 0) || die \"seek failed - $!\\n\";\n\n        my $cookie = { netcount => 0 };\n        while (defined(my $line = <$fh>)) {\n            $new_conf_raw .= restore_update_config_line(\n                $cookie, $map, $line, $options->{unique},\n            );\n        }\n\n        $fh->close();\n    };\n    my $err = $@;\n\n    if ($err || !$options->{live}) {\n        $restore_deactivate_volumes->($storecfg, $virtdev_hash);\n    }\n\n    rmtree $tmpdir;\n\n    if ($err) {\n        $restore_destroy_volumes->($storecfg, $virtdev_hash);\n        die $err;\n    }\n\n    if ($options->{live}) {\n        # keep lock during live-restore\n        $new_conf_raw .= \"\\nlock: create\";\n    }\n\n    my $new_conf = restore_merge_config($conffile, $new_conf_raw, $options->{override_conf});\n    check_restore_permissions($rpcenv, $user, $new_conf);\n    PVE::QemuConfig->write_config($vmid, $new_conf);\n\n    eval { rescan($vmid, 1); };\n    warn $@ if $@;\n\n    PVE::AccessControl::add_vm_to_pool($vmid, $options->{pool}) if $options->{pool};\n\n    if ($options->{live}) {\n        # enable interrupts\n        local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n            local $SIG{PIPE} = sub { die \"got signal ($!) - abort\\n\"; };\n\n        my $conf = PVE::QemuConfig->load_config($vmid);\n        die \"cannot do live-restore for template\\n\" if PVE::QemuConfig->is_template($conf);\n\n        # these special drives are already restored before start\n        delete $devinfo->{'drive-efidisk0'};\n        delete $devinfo->{'drive-tpmstate0-backup'};\n\n        my $pbs_opts = {\n            repo => $repo,\n            keyfile => $keyfile,\n            snapshot => $pbs_backup_name,\n            namespace => $namespace,\n        };\n        pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $pbs_opts);\n\n        PVE::QemuConfig->remove_lock($vmid, \"create\");\n    }\n}\n\nsub restore_external_archive {\n    my ($backup_provider, $archive, $vmid, $user, $options) = @_;\n\n    die \"live restore from backup provider is not implemented\\n\" if $options->{live};\n\n    my $storecfg = PVE::Storage::config();\n\n    my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive);\n    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n\n    my $tmpdir = \"/run/qemu-server/vzdumptmp$$\";\n    rmtree($tmpdir);\n    mkpath($tmpdir) or die \"unable to create $tmpdir\\n\";\n\n    my $conffile = PVE::QemuConfig->config_file($vmid);\n    # disable interrupts (always do cleanups)\n    local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n        sub { print STDERR \"got interrupt - ignored\\n\"; };\n\n    # Note: $oldconf is undef if VM does not exists\n    my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);\n    my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);\n    my $new_conf_raw = '';\n\n    my $rpcenv = PVE::RPCEnvironment::get();\n    my $devinfo = {}; # info about drives included in backup\n    my $virtdev_hash = {}; # info about allocated drives\n\n    eval {\n        # enable interrupts\n        local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n            local $SIG{PIPE} = sub { die \"interrupted by signal\\n\"; };\n\n        my $cfgfn = \"$tmpdir/qemu-server.conf\";\n        my $firewall_config_fn = \"$tmpdir/fw.conf\";\n\n        my $cmd = \"restore\";\n\n        my ($mechanism, $vmtype) = $backup_provider->restore_get_mechanism($volname);\n        die \"mechanism '$mechanism' requested by backup provider is not supported for VMs\\n\"\n            if $mechanism ne 'qemu-img';\n        die \"cannot restore non-VM guest of type '$vmtype'\\n\" if $vmtype ne 'qemu';\n\n        $devinfo = $backup_provider->restore_vm_init($volname);\n\n        my $data = $backup_provider->archive_get_guest_config($volname)\n            or die \"backup provider failed to extract guest configuration\\n\";\n        PVE::Tools::file_set_contents($cfgfn, $data);\n\n        if ($data = $backup_provider->archive_get_firewall_config($volname)) {\n            PVE::Tools::file_set_contents($firewall_config_fn, $data);\n            my $pve_firewall_dir = '/etc/pve/firewall';\n            mkdir $pve_firewall_dir; # make sure the dir exists\n            PVE::Tools::file_copy($firewall_config_fn, \"${pve_firewall_dir}/$vmid.fw\");\n        }\n\n        my $fh = IO::File->new($cfgfn, \"r\") or die \"unable to read qemu-server.conf - $!\\n\";\n\n        $virtdev_hash =\n            $parse_backup_hints->($rpcenv, $user, $storecfg, $fh, $devinfo, $options);\n\n        # create empty/temp config\n        PVE::Tools::file_set_contents($conffile, \"memory: 128\\nlock: create\");\n\n        $restore_cleanup_oldconf->($storecfg, $vmid, $oldconf, $virtdev_hash) if $oldconf;\n\n        # allocate volumes\n        my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid);\n\n        for my $virtdev (sort keys $virtdev_hash->%*) {\n            my $d = $virtdev_hash->{$virtdev};\n            next if $d->{is_cloudinit}; # no need to restore cloudinit\n\n            my $sparseinit =\n                PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $d->{volid});\n            my $source_format = 'raw';\n\n            my $info = $backup_provider->restore_vm_volume_init($volname, $d->{devname}, {});\n            my $source_path = $info->{'qemu-img-path'}\n                or die \"did not get source image path from backup provider\\n\";\n\n            print \"importing drive '$d->{devname}' from '$source_path'\\n\";\n\n            # safety check for untrusted source image\n            PVE::Storage::file_size_info($source_path, undef, $source_format, 1);\n\n            eval {\n                my $convert_opts = {\n                    bwlimit => $options->{bwlimit},\n                    'is-zero-initialized' => $sparseinit,\n                    'source-path-format' => $source_format,\n                };\n                PVE::QemuServer::QemuImage::convert(\n                    $source_path,\n                    $d->{volid},\n                    $d->{size},\n                    $convert_opts,\n                );\n            };\n            my $err = $@;\n            eval { $backup_provider->restore_vm_volume_cleanup($volname, $d->{devname}, {}); };\n            if (my $cleanup_err = $@) {\n                die $cleanup_err if !$err;\n                warn $cleanup_err;\n            }\n            die $err if $err;\n        }\n\n        $fh->seek(0, 0) || die \"seek failed - $!\\n\";\n\n        my $cookie = { netcount => 0 };\n        while (defined(my $line = <$fh>)) {\n            $new_conf_raw .= restore_update_config_line(\n                $cookie, $map, $line, $options->{unique},\n            );\n        }\n\n        $fh->close();\n    };\n    my $err = $@;\n\n    eval { $backup_provider->restore_vm_cleanup($volname); };\n    warn \"backup provider cleanup after restore failed - $@\" if $@;\n\n    if ($err) {\n        $restore_deactivate_volumes->($storecfg, $virtdev_hash);\n    }\n\n    rmtree($tmpdir);\n\n    if ($err) {\n        $restore_destroy_volumes->($storecfg, $virtdev_hash);\n        die $err;\n    }\n\n    my $new_conf = restore_merge_config($conffile, $new_conf_raw, $options->{override_conf});\n    check_restore_permissions($rpcenv, $user, $new_conf);\n    PVE::QemuConfig->write_config($vmid, $new_conf);\n\n    eval { rescan($vmid, 1); };\n    warn $@ if $@;\n\n    PVE::AccessControl::add_vm_to_pool($vmid, $options->{pool}) if $options->{pool};\n\n    return;\n}\n\nsub pbs_live_restore {\n    my ($vmid, $conf, $storecfg, $restored_disks, $opts) = @_;\n\n    print \"starting VM for live-restore\\n\";\n    print \"repository: '$opts->{repo}', snapshot: '$opts->{snapshot}'\\n\";\n\n    my $live_restore_backing = {};\n    for my $ds (keys %$restored_disks) {\n        $ds =~ m/^drive-(.*)$/;\n        my $confname = $1;\n        my $pbs_conf = {};\n        $pbs_conf = {\n            repository => $opts->{repo},\n            snapshot => $opts->{snapshot},\n            archive => \"$ds.img.fidx\",\n        };\n        $pbs_conf->{keyfile} = $opts->{keyfile} if -e $opts->{keyfile};\n        $pbs_conf->{namespace} = $opts->{namespace} if defined($opts->{namespace});\n\n        my $drive = parse_drive($confname, $conf->{$confname});\n        print \"restoring '$ds' to '$drive->{file}'\\n\";\n\n        my $pbs_name = \"drive-${confname}-pbs\";\n\n        $live_restore_backing->{$confname} = { name => $pbs_name };\n\n        # add blockdev information\n        my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf);\n        my $machine_version = PVE::QemuServer::Machine::extract_version(\n            $machine_type,\n            PVE::QemuServer::Helpers::kvm_user_version(),\n        );\n        if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev\n            $live_restore_backing->{$confname}->{blockdev} =\n                PVE::QemuServer::Blockdev::generate_pbs_blockdev($pbs_conf, $pbs_name);\n        } else {\n            $live_restore_backing->{$confname}->{blockdev} =\n                print_pbs_blockdev($pbs_conf, $pbs_name);\n        }\n    }\n\n    my $drives_streamed = 0;\n    eval {\n        # make sure HA doesn't interrupt our restore by stopping the VM\n        if (vm_is_ha_managed($vmid)) {\n            run_command(['ha-manager', 'set', \"vm:$vmid\", '--state', 'started']);\n        }\n\n        # start VM with backing chain pointing to PBS backup, environment vars for PBS driver\n        # in QEMU (PBS_PASSWORD and PBS_FINGERPRINT) are already set by our caller\n        vm_start_nolock(\n            $storecfg,\n            $vmid,\n            $conf,\n            { paused => 1, 'live-restore-backing' => $live_restore_backing },\n            {},\n        );\n\n        my $qmeventd_fd = register_qmeventd_handle($vmid);\n\n        # begin streaming, i.e. data copy from PBS to target disk for every vol,\n        # this will effectively collapse the backing image chain consisting of\n        # [target <- alloc-track -> PBS snapshot] to just [target] (alloc-track\n        # removes itself once all backing images vanish with 'auto-remove=on')\n        my $jobs = {};\n        for my $ds (sort keys %$restored_disks) {\n            my $node_name =\n                PVE::QemuServer::Blockdev::get_node_name_below_throttle(vm_qmp_peer($vmid), $ds);\n            my $job_id = \"restore-$ds\";\n            mon_cmd(\n                $vmid, 'block-stream',\n                'job-id' => $job_id,\n                device => \"$node_name\",\n                'auto-dismiss' => JSON::false,\n            );\n            $jobs->{$job_id} = {};\n        }\n\n        mon_cmd($vmid, 'cont');\n        PVE::QemuServer::BlockJob::monitor(\n            vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',\n        );\n\n        print \"restore-drive jobs finished successfully, removing all tracking block devices\"\n            . \" to disconnect from Proxmox Backup Server\\n\";\n\n        for my $ds (sort keys %$restored_disks) {\n            PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), \"$ds-pbs\");\n        }\n\n        close($qmeventd_fd);\n    };\n\n    my $err = $@;\n\n    if ($err) {\n        warn \"An error occurred during live-restore: $err\\n\";\n        _do_vm_stop($storecfg, $vmid, 1, 1, 10, 0, 1);\n        die \"live-restore failed\\n\";\n    }\n}\n\n# Inspired by pbs live-restore, this restores with the disks being available as files.\n# Theoretically this can also be used to quick-start a full-clone vm if the\n# disks are all available as files.\n#\n# The mapping should provide a path by config entry, such as\n# `{ scsi0 => { format => <qcow2|raw|...>, path => \"/path/to/file\", sata1 => ... } }`\n#\n# This is used when doing a `create` call with the `--live-import` parameter,\n# where the disks get an `import-from=` property. The non-live part is\n# therefore already handled in the `$create_disks()` call happening in the\n# `create` api call\nsub live_import_from_files {\n    my ($mapping, $vmid, $conf, $restore_options) = @_;\n\n    my $storecfg = PVE::Storage::config();\n\n    my $live_restore_backing = {};\n    my $sources_to_remove = [];\n    for my $dev (keys %$mapping) {\n        die \"disk not support for live-restoring: '$dev'\\n\"\n            if !is_valid_drivename($dev) || $dev =~ /^(?:efidisk|tpmstate)/;\n\n        die \"mapping contains disk '$dev' which does not exist in the config\\n\"\n            if !exists($conf->{$dev});\n\n        my $info = $mapping->{$dev};\n        my ($format, $path, $volid) = $info->@{qw(format path volid)};\n        die \"missing path for '$dev' mapping\\n\" if !$path;\n        die \"missing volid for '$dev' mapping\\n\" if !$volid;\n        die \"missing format for '$dev' mapping\\n\" if !$format;\n        die \"invalid format '$format' for '$dev' mapping\\n\"\n            if !grep { $format eq $_ } qw(raw qcow2 vmdk);\n\n        $live_restore_backing->{$dev} = { name => \"drive-$dev-restore\" };\n\n        my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf);\n        my $machine_version = PVE::QemuServer::Machine::extract_version(\n            $machine_type,\n            PVE::QemuServer::Helpers::kvm_user_version(),\n        );\n        if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev\n            my ($interface, $index) = PVE::QemuServer::Drive::parse_drive_interface($dev);\n            my $drive = { file => $volid, interface => $interface, index => $index };\n            my $blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(\n                $storecfg,\n                $drive,\n                $machine_version,\n                { 'no-throttle' => 1 },\n            );\n            $live_restore_backing->{$dev}->{blockdev} = $blockdev;\n        } else {\n            $live_restore_backing->{$dev}->{blockdev} =\n                \"driver=$format,node-name=drive-$dev-restore\"\n                . \",read-only=on\"\n                . \",file.driver=file,file.filename=$path\";\n        }\n\n        my $source_volid = $info->{'delete-after-finish'};\n        push $sources_to_remove->@*, $source_volid if defined($source_volid);\n    }\n\n    eval {\n\n        # make sure HA doesn't interrupt our restore by stopping the VM\n        if (vm_is_ha_managed($vmid)) {\n            run_command(['ha-manager', 'set', \"vm:$vmid\", '--state', 'started']);\n        }\n\n        vm_start_nolock(\n            $storecfg,\n            $vmid,\n            $conf,\n            { paused => 1, 'live-restore-backing' => $live_restore_backing },\n            {},\n        );\n\n        # prevent shutdowns from qmeventd when the VM powers off from the inside\n        my $qmeventd_fd = register_qmeventd_handle($vmid);\n\n        # begin streaming, i.e. data copy from PBS to target disk for every vol,\n        # this will effectively collapse the backing image chain consisting of\n        # [target <- alloc-track -> PBS snapshot] to just [target] (alloc-track\n        # removes itself once all backing images vanish with 'auto-remove=on')\n        my $jobs = {};\n        for my $ds (sort keys %$live_restore_backing) {\n            my $node_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(\n                vm_qmp_peer($vmid), \"drive-$ds\",\n            );\n            my $job_id = \"restore-$ds\";\n            mon_cmd(\n                $vmid, 'block-stream',\n                'job-id' => $job_id,\n                device => \"$node_name\",\n                'auto-dismiss' => JSON::false,\n            );\n            $jobs->{$job_id} = {};\n        }\n\n        mon_cmd($vmid, 'cont');\n        PVE::QemuServer::BlockJob::monitor(\n            vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',\n        );\n\n        print \"restore-drive jobs finished successfully, removing all tracking block devices\\n\";\n\n        for my $ds (sort keys %$live_restore_backing) {\n            PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), \"drive-$ds-restore\");\n        }\n\n        close($qmeventd_fd);\n    };\n\n    my $err = $@;\n\n    for my $volid ($sources_to_remove->@*) {\n        eval {\n            PVE::Storage::vdisk_free($storecfg, $volid);\n            print \"cleaned up extracted image $volid\\n\";\n        };\n        warn \"An error occurred while cleaning up source images: $@\\n\" if $@;\n    }\n\n    if ($err) {\n        warn \"An error occurred during live-restore: $err\\n\";\n        _do_vm_stop($storecfg, $vmid, 1, 1, 10, 0, 1);\n        die \"live-restore failed\\n\";\n    }\n\n    PVE::QemuConfig->remove_lock($vmid, \"import\");\n}\n\nsub restore_vma_archive {\n    my ($archive, $vmid, $user, $opts, $comp) = @_;\n\n    my $readfrom = $archive;\n\n    my $cfg = PVE::Storage::config();\n    my $commands = [];\n    my $bwlimit = $opts->{bwlimit};\n\n    my $dbg_cmdstring = '';\n    my $add_pipe = sub {\n        my ($cmd) = @_;\n        push @$commands, $cmd;\n        $dbg_cmdstring .= ' | ' if length($dbg_cmdstring);\n        $dbg_cmdstring .= PVE::Tools::cmd2string($cmd);\n        $readfrom = '-';\n    };\n\n    my $input = undef;\n    if ($archive eq '-') {\n        $input = '<&STDIN';\n    } else {\n        # If we use a backup from a PVE defined storage we also consider that\n        # storage's rate limit:\n        my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive);\n        if (defined($volid)) {\n            my ($sid, undef) = PVE::Storage::parse_volume_id($volid);\n            my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit);\n            if ($readlimit) {\n                print STDERR \"applying read rate limit: $readlimit\\n\";\n                my $cstream = ['cstream', '-t', $readlimit * 1024, '--', $readfrom];\n                $add_pipe->($cstream);\n            }\n        }\n    }\n\n    if ($comp) {\n        my $info = PVE::Storage::decompressor_info('vma', $comp);\n        my $cmd = $info->{decompressor};\n        push @$cmd, $readfrom;\n        $add_pipe->($cmd);\n    }\n\n    my $tmpdir = \"/var/tmp/vzdumptmp$$\";\n    rmtree $tmpdir;\n\n    # disable interrupts (always do cleanups)\n    local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n        sub { warn \"got interrupt - ignored\\n\"; };\n\n    my $mapfifo = \"/var/tmp/vzdumptmp$$.fifo\";\n    POSIX::mkfifo($mapfifo, 0600);\n    my $fifofh;\n    my $openfifo = sub { open($fifofh, '>', $mapfifo) or die $! };\n\n    $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);\n\n    my $devinfo = {}; # info about drives included in backup\n    my $virtdev_hash = {}; # info about allocated drives\n\n    my $rpcenv = PVE::RPCEnvironment::get();\n\n    my $conffile = PVE::QemuConfig->config_file($vmid);\n\n    # Note: $oldconf is undef if VM does not exist\n    my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);\n    my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);\n    my $new_conf_raw = '';\n\n    my %storage_limits;\n\n    my $print_devmap = sub {\n        my $cfgfn = \"$tmpdir/qemu-server.conf\";\n\n        # we can read the config - that is already extracted\n        my $fh = IO::File->new($cfgfn, \"r\")\n            || die \"unable to read qemu-server.conf - $!\\n\";\n\n        my $fwcfgfn = \"$tmpdir/qemu-server.fw\";\n        if (-f $fwcfgfn) {\n            my $pve_firewall_dir = '/etc/pve/firewall';\n            mkdir $pve_firewall_dir; # make sure the dir exists\n            PVE::Tools::file_copy($fwcfgfn, \"${pve_firewall_dir}/$vmid.fw\");\n        }\n\n        $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $cfg, $fh, $devinfo, $opts);\n\n        foreach my $info (values %{$virtdev_hash}) {\n            my $storeid = $info->{storeid};\n            next if defined($storage_limits{$storeid});\n\n            my $limit = PVE::Storage::get_bandwidth_limit('restore', [$storeid], $bwlimit) // 0;\n            print STDERR \"rate limit for storage $storeid: $limit KiB/s\\n\" if $limit;\n            $storage_limits{$storeid} = $limit * 1024;\n        }\n\n        foreach my $devname (keys %$devinfo) {\n            die \"found no device mapping information for device '$devname'\\n\"\n                if !$devinfo->{$devname}->{virtdev};\n        }\n\n        # create empty/temp config\n        if ($oldconf) {\n            PVE::Tools::file_set_contents($conffile, \"memory: 128\\n\");\n            $restore_cleanup_oldconf->($cfg, $vmid, $oldconf, $virtdev_hash);\n        }\n\n        # allocate volumes\n        my $map = $restore_allocate_devices->($cfg, $virtdev_hash, $vmid);\n\n        # print restore information to $fifofh\n        foreach my $virtdev (sort keys %$virtdev_hash) {\n            my $d = $virtdev_hash->{$virtdev};\n            next if $d->{is_cloudinit}; # no need to restore cloudinit\n\n            my $storeid = $d->{storeid};\n            my $volid = $d->{volid};\n\n            my $map_opts = '';\n            if (my $limit = $storage_limits{$storeid}) {\n                $map_opts .= \"throttling.bps=$limit:throttling.group=$storeid:\";\n            }\n\n            my $write_zeros = 1;\n            if (PVE::Storage::volume_has_feature($cfg, 'sparseinit', $volid)) {\n                $write_zeros = 0;\n            }\n\n            my $path = PVE::Storage::path($cfg, $volid);\n\n            print $fifofh \"${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\\n\";\n\n            print \"map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\\n\";\n        }\n\n        $fh->seek(0, 0) || die \"seek failed - $!\\n\";\n\n        my $cookie = { netcount => 0 };\n        while (defined(my $line = <$fh>)) {\n            $new_conf_raw .= restore_update_config_line(\n                $cookie, $map, $line, $opts->{unique},\n            );\n        }\n\n        $fh->close();\n    };\n\n    my $oldtimeout;\n\n    eval {\n        my $timeout_message = \"got timeout preparing VMA restore\\n\";\n        # enable interrupts\n        local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n            local $SIG{PIPE} = sub { die \"interrupted by signal\\n\"; };\n        local $SIG{ALRM} = sub { die $timeout_message; };\n\n        $oldtimeout = alarm(60); # for reading the VMA header - might hang with a corrupted one\n        $timeout_message = \"got timeout reading VMA header - corrupted?\\n\";\n\n        my $parser = sub {\n            my $line = shift;\n\n            print \"$line\\n\";\n\n            if ($line =~ m/^DEV:\\sdev_id=(\\d+)\\ssize:\\s(\\d+)\\sdevname:\\s(\\S+)$/) {\n                my ($dev_id, $size, $devname) = ($1, $2, $3);\n                $devinfo->{$devname} = { size => $size, dev_id => $dev_id };\n            } elsif ($line =~ m/^CTIME: /) {\n                $timeout_message = \"got timeout during VMA restore\\n\";\n                # we correctly received the vma config, so we can disable\n                # the timeout now for disk allocation\n                alarm($oldtimeout || 0);\n                $oldtimeout = undef;\n                &$print_devmap();\n                print $fifofh \"done\\n\";\n                close($fifofh);\n                $fifofh = undef;\n            }\n        };\n\n        print \"restore vma archive: $dbg_cmdstring\\n\";\n        run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);\n    };\n    my $err = $@;\n\n    alarm($oldtimeout) if $oldtimeout;\n\n    $restore_deactivate_volumes->($cfg, $virtdev_hash);\n\n    close($fifofh) if $fifofh;\n    unlink $mapfifo;\n    rmtree $tmpdir;\n\n    if ($err) {\n        $restore_destroy_volumes->($cfg, $virtdev_hash);\n        die $err;\n    }\n\n    my $new_conf = restore_merge_config($conffile, $new_conf_raw, $opts->{override_conf});\n    check_restore_permissions($rpcenv, $user, $new_conf);\n    PVE::QemuConfig->write_config($vmid, $new_conf);\n\n    eval { rescan($vmid, 1); };\n    warn $@ if $@;\n\n    PVE::AccessControl::add_vm_to_pool($vmid, $opts->{pool}) if $opts->{pool};\n}\n\nsub restore_tar_archive {\n    my ($archive, $vmid, $user, $opts) = @_;\n\n    if (scalar(keys $opts->{override_conf}->%*) > 0) {\n        my $keystring = join(' ', keys $opts->{override_conf}->%*);\n        die \"cannot pass along options ($keystring) when restoring from tar archive\\n\";\n    }\n\n    if ($archive ne '-') {\n        my $firstfile = tar_archive_read_firstfile($archive);\n        die \"ERROR: file '$archive' does not look like a QemuServer vzdump backup\\n\"\n            if $firstfile ne 'qemu-server.conf';\n    }\n\n    my $storecfg = PVE::Storage::config();\n\n    # avoid zombie disks when restoring over an existing VM -> cleanup first\n    # pass keep_empty_config=1 to keep the config (thus VMID) reserved for us\n    # skiplock=1 because qmrestore has set the 'create' lock itself already\n    my $vmcfgfn = PVE::QemuConfig->config_file($vmid);\n    destroy_vm($storecfg, $vmid, 1, { lock => 'restore' }) if -f $vmcfgfn;\n\n    my $tocmd = \"/usr/lib/qemu-server/qmextract\";\n\n    $tocmd .= \" --storage \" . PVE::Tools::shellquote($opts->{storage}) if $opts->{storage};\n    $tocmd .= \" --pool \" . PVE::Tools::shellquote($opts->{pool}) if $opts->{pool};\n    $tocmd .= ' --prealloc' if $opts->{prealloc};\n    $tocmd .= ' --info' if $opts->{info};\n\n    # tar option \"xf\" does not autodetect compression when read from STDIN,\n    # so we pipe to zcat\n    my $cmd =\n        \"zcat -f|tar xf \"\n        . PVE::Tools::shellquote($archive) . \" \"\n        . PVE::Tools::shellquote(\"--to-command=$tocmd\");\n\n    my $tmpdir = \"/var/tmp/vzdumptmp$$\";\n    mkpath $tmpdir;\n\n    local $ENV{VZDUMP_TMPDIR} = $tmpdir;\n    local $ENV{VZDUMP_VMID} = $vmid;\n    local $ENV{VZDUMP_USER} = $user;\n\n    my $conffile = PVE::QemuConfig->config_file($vmid);\n    my $new_conf_raw = '';\n\n    # disable interrupts (always do cleanups)\n    local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n        sub { print STDERR \"got interrupt - ignored\\n\"; };\n\n    eval {\n        # enable interrupts\n        local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = local $SIG{HUP} =\n            local $SIG{PIPE} = sub { die \"interrupted by signal\\n\"; };\n\n        if ($archive eq '-') {\n            print \"extracting archive from STDIN\\n\";\n            run_command($cmd, input => \"<&STDIN\");\n        } else {\n            print \"extracting archive '$archive'\\n\";\n            run_command($cmd);\n        }\n\n        return if $opts->{info};\n\n        # read new mapping\n        my $map = {};\n        my $statfile = \"$tmpdir/qmrestore.stat\";\n        if (my $fd = IO::File->new($statfile, \"r\")) {\n            while (defined(my $line = <$fd>)) {\n                if ($line =~ m/vzdump:([^\\s:]*):(\\S+)$/) {\n                    $map->{$1} = $2 if $1;\n                } else {\n                    print STDERR \"unable to parse line in statfile - $line\\n\";\n                }\n            }\n            $fd->close();\n        }\n\n        my $confsrc = \"$tmpdir/qemu-server.conf\";\n\n        my $srcfd = IO::File->new($confsrc, \"r\") || die \"unable to open file '$confsrc'\\n\";\n\n        my $cookie = { netcount => 0 };\n        while (defined(my $line = <$srcfd>)) {\n            $new_conf_raw .= restore_update_config_line(\n                $cookie, $map, $line, $opts->{unique},\n            );\n        }\n\n        $srcfd->close();\n    };\n    if (my $err = $@) {\n        tar_restore_cleanup($storecfg, \"$tmpdir/qmrestore.stat\") if !$opts->{info};\n        die $err;\n    }\n\n    rmtree $tmpdir;\n\n    PVE::Tools::file_set_contents($conffile, $new_conf_raw);\n\n    PVE::Cluster::cfs_update(); # make sure we read new file\n\n    eval { rescan($vmid, 1); };\n    warn $@ if $@;\n}\n\nsub do_snapshots_type {\n    my ($storecfg, $volid, $deviceid, $running) = @_;\n\n    #we use storage snapshot if vm is not running or if disk is unused;\n    return 'storage' if !$running || !$deviceid;\n\n    if (my $method = PVE::Storage::volume_qemu_snapshot_method($storecfg, $volid)) {\n        return 'internal' if $method eq 'qemu';\n        return 'external' if $method eq 'mixed';\n    }\n    return 'storage';\n}\n\n=head3 template_create($vmid, $conf [, $disk])\n\nConverts all used disk volumes for the VM with the identifier C<$vmid> and\nconfiguration C<$conf> to base images (e.g. for VM templates).\n\nIf the optional C<$disk> parameter is set, it will only convert the disk\nvolume at the specified drive name (e.g. \"scsi0\").\n\n=cut\n\nsub template_create : prototype($$;$) {\n    my ($vmid, $conf, $disk) = @_;\n\n    my $storecfg = PVE::Storage::config();\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            return if drive_is_cdrom($drive);\n            return if $disk && $ds ne $disk;\n\n            my $volid = $drive->{file};\n            return if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);\n\n            my $voliddst = PVE::Storage::vdisk_create_base($storecfg, $volid);\n            $drive->{file} = $voliddst;\n            $conf->{$ds} = print_drive($drive);\n\n            # write vm config on every change in case this fails on subsequent iterations\n            PVE::QemuConfig->write_config($vmid, $conf);\n        },\n    );\n}\n\n# Check for bug #4525: drive-mirror will open the target drive with the same aio setting as the\n# source, but some storages have problems with io_uring, sometimes even leading to crashes.\nmy sub clone_disk_check_io_uring {\n    my ($vmid, $src_drive, $storecfg, $src_storeid, $dst_storeid, $use_drive_mirror) = @_;\n\n    return if !$use_drive_mirror;\n\n    # Don't complain when not changing storage.\n    # Assume if it works for the source, it'll work for the target too.\n    return if $src_storeid eq $dst_storeid;\n\n    my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);\n    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);\n\n    my $cache_direct = PVE::QemuServer::Drive::drive_uses_cache_direct($src_drive, $src_scfg);\n\n    my $src_uses_io_uring;\n    if ($src_drive->{aio}) {\n        $src_uses_io_uring = $src_drive->{aio} eq 'io_uring';\n    } else {\n        # With the switch to -blockdev and blockdev-mirror, the aio setting will be changed on the\n        # fly if not explicitly set.\n        my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);\n        return if PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0);\n\n        $src_uses_io_uring = storage_allows_io_uring_default($src_scfg, $cache_direct);\n    }\n\n    die \"target storage is known to cause issues with aio=io_uring (used by current drive)\\n\"\n        if $src_uses_io_uring && !storage_allows_io_uring_default($dst_scfg, $cache_direct);\n}\n\nsub clone_disk {\n    my ($storecfg, $source, $dest, $full, $newvollist, $jobs, $completion, $qga, $bwlimit) = @_;\n\n    my ($vmid, $running) = $source->@{qw(vmid running)};\n    my ($src_drivename, $drive, $snapname) = $source->@{qw(drivename drive snapname)};\n\n    my ($newvmid, $dst_drivename, $efisize) = $dest->@{qw(vmid drivename efisize)};\n    my ($storage, $format) = $dest->@{qw(storage format)};\n\n    my $unused = defined($src_drivename) && $src_drivename =~ /^unused/;\n    my $use_drive_mirror = $full && $running && $src_drivename && !$snapname && !$unused;\n\n    if ($src_drivename && $dst_drivename && $src_drivename ne $dst_drivename) {\n        die \"cloning from/to EFI disk requires EFI disk\\n\"\n            if $src_drivename eq 'efidisk0' || $dst_drivename eq 'efidisk0';\n        die \"cloning from/to TPM state requires TPM state\\n\"\n            if $src_drivename eq 'tpmstate0' || $dst_drivename eq 'tpmstate0';\n\n        # This would lead to two device nodes in QEMU pointing to the same backing image!\n        die \"cannot change drive name when cloning disk from/to the same VM\\n\"\n            if $use_drive_mirror && $vmid == $newvmid;\n    }\n\n    die \"cannot move TPM state while VM is running\\n\"\n        if $use_drive_mirror && $src_drivename eq 'tpmstate0';\n\n    my $newvolid;\n\n    print \"create \" . ($full ? 'full' : 'linked') . \" clone of drive \";\n    print \"$src_drivename \" if $src_drivename;\n    print \"($drive->{file})\\n\";\n\n    if (!$full) {\n        $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newvmid, $snapname);\n        push @$newvollist, $newvolid;\n    } else {\n        my ($src_storeid) = PVE::Storage::parse_volume_id($drive->{file});\n        my $storeid = $storage || $src_storeid;\n\n        my $dst_format = resolve_dst_disk_format($storecfg, $storeid, $drive->{file}, $format);\n\n        my $name = undef;\n        my $size = undef;\n        if (drive_is_cloudinit($drive)) {\n            $name = \"vm-$newvmid-cloudinit\";\n            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n            if ($scfg->{path}) {\n                $name .= \".$dst_format\";\n            }\n            $snapname = undef;\n            $size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE;\n        } elsif ($dst_drivename eq 'efidisk0') {\n            $size = $efisize or die \"internal error - need to specify EFI disk size\\n\";\n        } elsif ($dst_drivename eq 'tpmstate0') {\n            $size = PVE::QemuServer::Drive::TPMSTATE_DISK_SIZE;\n        } else {\n            clone_disk_check_io_uring(\n                $vmid, $drive, $storecfg, $src_storeid, $storeid, $use_drive_mirror,\n            );\n\n            $size = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 10);\n        }\n        $newvolid = PVE::Storage::vdisk_alloc(\n            $storecfg,\n            $storeid,\n            $newvmid,\n            $dst_format,\n            $name,\n            ($size / 1024),\n        );\n        push @$newvollist, $newvolid;\n\n        print(\"allocated target volume '$newvolid'\\n\");\n\n        PVE::Storage::activate_volumes($storecfg, [$newvolid]);\n\n        if (drive_is_cloudinit($drive)) {\n            # when cloning multiple disks (e.g. during clone_vm) it might be the last disk\n            # if this is the case, we have to complete any block-jobs still there from\n            # previous drive-mirrors\n            if (($completion && $completion eq 'complete') && (scalar(keys %$jobs) > 0)) {\n                PVE::QemuServer::BlockJob::monitor(\n                    vm_qmp_peer($vmid), $newvmid, $jobs, $completion, $qga,\n                );\n            }\n            goto no_data_clone;\n        }\n\n        my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);\n        if ($use_drive_mirror) {\n            my $source_info = { vmid => $vmid, drive => $drive };\n            my $dest_info = { volid => $newvolid };\n            $dest_info->{'zero-initialized'} = 1 if $sparseinit;\n            $dest_info->{vmid} = $newvmid if defined($newvmid);\n            my $mirror_opts = {};\n            $mirror_opts->{'guest-agent'} = 1 if $qga;\n            $mirror_opts->{bwlimit} = $bwlimit if defined($bwlimit);\n            PVE::QemuServer::BlockJob::mirror(\n                $source_info,\n                $dest_info,\n                $jobs,\n                $completion,\n                $mirror_opts,\n            );\n        } else {\n            if ($dst_drivename eq 'efidisk0') {\n                # the relevant data on the efidisk may be smaller than the source\n                # e.g. on RBD/ZFS, so we use dd to copy only the amount\n                # that is given by the OVMF_VARS.fd\n                my $src_path = PVE::Storage::path($storecfg, $drive->{file}, $snapname);\n                my $dst_path = PVE::Storage::path($storecfg, $newvolid);\n\n                my $src_format = (PVE::Storage::parse_volname($storecfg, $drive->{file}))[6];\n\n                # better for Ceph if block size is not too small, see bug #3324\n                my $bs = 1024 * 1024;\n\n                my $cmd = ['qemu-img', 'dd', '-n', '-f', $src_format, '-O', $dst_format];\n\n                if ($src_format eq 'qcow2' && $snapname) {\n                    die \"cannot clone qcow2 EFI disk snapshot - requires QEMU >= 6.2\\n\"\n                        if !min_version(kvm_user_version(), 6, 2);\n\n                    my $method =\n                        PVE::Storage::volume_qemu_snapshot_method($storecfg, $drive->{file});\n                    # in case of snapshot-as-volume-chain, $src_path points to the snapshot volume\n                    push $cmd->@*, '-l', $snapname if $method eq 'qemu';\n                }\n                push $cmd->@*, \"bs=$bs\", \"osize=$size\", \"if=$src_path\", \"of=$dst_path\";\n                run_command($cmd);\n            } else {\n                my $opts = {\n                    bwlimit => $bwlimit,\n                    'is-zero-initialized' => $sparseinit,\n                    snapname => $snapname,\n                };\n                PVE::QemuServer::QemuImage::convert($drive->{file}, $newvolid, $size, $opts);\n            }\n        }\n    }\n\nno_data_clone:\n    my $size = eval { PVE::Storage::volume_size_info($storecfg, $newvolid, 10) };\n\n    my $disk = dclone($drive);\n    delete $disk->{format};\n    $disk->{file} = $newvolid;\n    $disk->{size} = $size if defined($size) && !$unused;\n\n    return $disk;\n}\n\nsub get_running_qemu_version {\n    my ($vmid) = @_;\n    my $res = mon_cmd($vmid, \"query-version\");\n    return \"$res->{qemu}->{major}.$res->{qemu}->{minor}\";\n}\n\nsub qemu_use_old_bios_files {\n    my ($machine_type) = @_;\n\n    return if !$machine_type;\n\n    my $use_old_bios_files = undef;\n\n    if ($machine_type =~ m/^(\\S+)\\.pxe$/) {\n        $machine_type = $1;\n        $use_old_bios_files = 1;\n    } else {\n        my $version = extract_version($machine_type, kvm_user_version());\n        # Note: kvm version < 2.4 use non-efi pxe files, and have problems when we\n        # load new efi bios files on migration. So this hack is required to allow\n        # live migration from qemu-2.2 to qemu-2.4, which is sometimes used when\n        # updrading from proxmox-ve-3.X to proxmox-ve 4.0\n        $use_old_bios_files = !min_version($version, 2, 4);\n    }\n\n    return ($use_old_bios_files, $machine_type);\n}\n\nsub get_efivars_size {\n    my ($conf, $efidisk) = @_;\n\n    my $arch = PVE::QemuServer::Helpers::get_vm_arch($conf);\n    $efidisk //= $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;\n    my $smm = PVE::QemuServer::Machine::machine_type_is_q35($conf);\n    my $cvm_type = get_cvm_type($conf);\n\n    return PVE::QemuServer::OVMF::get_efivars_size($arch, $efidisk, $smm, $cvm_type);\n}\n\nsub update_efidisk_size {\n    my ($conf) = @_;\n\n    return if !defined($conf->{efidisk0});\n\n    my $disk = PVE::QemuServer::parse_drive('efidisk0', $conf->{efidisk0});\n    $disk->{size} = get_efivars_size($conf);\n    $conf->{efidisk0} = print_drive($disk);\n\n    return;\n}\n\nsub update_tpmstate_size {\n    my ($conf) = @_;\n\n    my $disk = PVE::QemuServer::parse_drive('tpmstate0', $conf->{tpmstate0});\n    $disk->{size} = PVE::QemuServer::Drive::TPMSTATE_DISK_SIZE;\n    $conf->{tpmstate0} = print_drive($disk);\n}\n\nsub vm_iothreads_list {\n    my ($vmid) = @_;\n\n    my $res = mon_cmd($vmid, 'query-iothreads');\n\n    my $iothreads = {};\n    foreach my $iothread (@$res) {\n        $iothreads->{ $iothread->{id} } = $iothread->{\"thread-id\"};\n    }\n\n    return $iothreads;\n}\n\nsub resolve_dst_disk_format {\n    my ($storecfg, $storeid, $src_volid, $format) = @_;\n\n    # if no target format is specified, use the source disk format as hint\n    if (!$format && $src_volid) {\n        $format = checked_volume_format($storecfg, $src_volid);\n    }\n\n    # falls back to default format if requested format is not supported\n    return PVE::Storage::resolve_format_hint($storecfg, $storeid, $format);\n}\n\nsub generate_uuid {\n    my ($uuid, $uuid_str);\n    UUID::generate($uuid);\n    UUID::unparse($uuid, $uuid_str);\n    return $uuid_str;\n}\n\nsub generate_smbios1_uuid {\n    return \"uuid=\" . generate_uuid();\n}\n\nsub create_reboot_request {\n    my ($vmid) = @_;\n    open(my $fh, '>', \"/run/qemu-server/$vmid.reboot\")\n        or die \"failed to create reboot trigger file: $!\\n\";\n    close($fh);\n}\n\nsub clear_reboot_request {\n    my ($vmid) = @_;\n    my $path = \"/run/qemu-server/$vmid.reboot\";\n    my $res = 0;\n\n    $res = unlink($path);\n    die \"could not remove reboot request for $vmid: $!\"\n        if !$res && $! != POSIX::ENOENT;\n\n    return $res;\n}\n\nsub bootorder_from_legacy {\n    my ($conf, $bootcfg) = @_;\n\n    my $boot = $bootcfg->{legacy} || $boot_fmt->{legacy}->{default};\n    my $bootindex_hash = {};\n    my $i = 1;\n    foreach my $o (split(//, $boot)) {\n        $bootindex_hash->{$o} = $i * 100;\n        $i++;\n    }\n\n    my $bootorder = {};\n\n    PVE::QemuConfig->foreach_volume(\n        $conf,\n        sub {\n            my ($ds, $drive) = @_;\n\n            if (drive_is_cdrom($drive, 1)) {\n                if ($bootindex_hash->{d}) {\n                    $bootorder->{$ds} = $bootindex_hash->{d};\n                    $bootindex_hash->{d} += 1;\n                }\n            } elsif ($bootindex_hash->{c}) {\n                $bootorder->{$ds} = $bootindex_hash->{c}\n                    if $conf->{bootdisk} && $conf->{bootdisk} eq $ds;\n                $bootindex_hash->{c} += 1;\n            }\n        },\n    );\n\n    if ($bootindex_hash->{n}) {\n        for (my $i = 0; $i < $MAX_NETS; $i++) {\n            my $netname = \"net$i\";\n            next if !$conf->{$netname};\n            $bootorder->{$netname} = $bootindex_hash->{n};\n            $bootindex_hash->{n} += 1;\n        }\n    }\n\n    return $bootorder;\n}\n\n# Generate default device list for 'boot: order=' property. Matches legacy\n# default boot order, but with explicit device names. This is important, since\n# the fallback for when neither 'order' nor the old format is specified relies\n# on 'bootorder_from_legacy' above, and it would be confusing if this diverges.\nsub get_default_bootdevices {\n    my ($conf) = @_;\n\n    my @ret = ();\n\n    # harddisk\n    my $first = PVE::QemuServer::Drive::resolve_first_disk($conf, 0);\n    push @ret, $first if $first;\n\n    # cdrom\n    $first = PVE::QemuServer::Drive::resolve_first_disk($conf, 1);\n    push @ret, $first if $first;\n\n    # network\n    for (my $i = 0; $i < $MAX_NETS; $i++) {\n        my $netname = \"net$i\";\n        next if !$conf->{$netname};\n        push @ret, $netname;\n        last;\n    }\n\n    return \\@ret;\n}\n\nsub device_bootorder {\n    my ($conf) = @_;\n\n    return bootorder_from_legacy($conf) if !defined($conf->{boot});\n\n    my $boot = parse_property_string($boot_fmt, $conf->{boot});\n\n    my $bootorder = {};\n    if (!defined($boot) || $boot->{legacy}) {\n        $bootorder = bootorder_from_legacy($conf, $boot);\n    } elsif ($boot->{order}) {\n        my $i = 100; # start at 100 to allow user to insert devices before us with -args\n        for my $dev (PVE::Tools::split_list($boot->{order})) {\n            $bootorder->{$dev} = $i++;\n        }\n    }\n\n    return $bootorder;\n}\n\nsub register_qmeventd_handle {\n    my ($vmid) = @_;\n\n    my $fh;\n    my $peer = \"/var/run/qmeventd.sock\";\n    my $count = 0;\n\n    for (;;) {\n        $count++;\n        $fh = IO::Socket::UNIX->new(Peer => $peer, Blocking => 0, Timeout => 1);\n        last if $fh;\n        if ($! != EINTR && $! != EAGAIN) {\n            die \"unable to connect to qmeventd socket (vmid: $vmid) - $!\\n\";\n        }\n        if ($count > 4) {\n            die \"unable to connect to qmeventd socket (vmid: $vmid) - timeout \"\n                . \"after $count retries\\n\";\n        }\n        usleep(25000);\n    }\n\n    # send handshake to mark VM as backing up\n    print $fh to_json({ vzdump => { vmid => \"$vmid\" } });\n\n    # return handle to be closed later when inhibit is no longer required\n    return $fh;\n}\n\n# bash completion helper\n\nsub complete_backup_archives {\n    my ($cmdname, $pname, $cvalue) = @_;\n\n    my $cfg = PVE::Storage::config();\n\n    my $storeid;\n\n    if ($cvalue =~ m/^([^:]+):/) {\n        $storeid = $1;\n    }\n\n    my $data = PVE::Storage::template_list($cfg, $storeid, 'backup');\n\n    my $res = [];\n    foreach my $id (keys %$data) {\n        foreach my $item (@{ $data->{$id} }) {\n            next if ($item->{subtype} // '') ne 'qemu';\n            push @$res, $item->{volid} if defined($item->{volid});\n        }\n    }\n\n    return $res;\n}\n\nmy $complete_vmid_full = sub {\n    my ($running) = @_;\n\n    my $idlist = vmstatus();\n\n    my $res = [];\n\n    foreach my $id (keys %$idlist) {\n        my $d = $idlist->{$id};\n        if (defined($running)) {\n            next if $d->{template};\n            next if $running && $d->{status} ne 'running';\n            next if !$running && $d->{status} eq 'running';\n        }\n        push @$res, $id;\n\n    }\n    return $res;\n};\n\nsub complete_vmid {\n    return &$complete_vmid_full();\n}\n\nsub complete_vmid_stopped {\n    return &$complete_vmid_full(0);\n}\n\nsub complete_vmid_running {\n    return &$complete_vmid_full(1);\n}\n\nsub complete_storage {\n\n    my $cfg = PVE::Storage::config();\n    my $ids = $cfg->{ids};\n\n    my $res = [];\n    foreach my $sid (keys %$ids) {\n        next if !PVE::Storage::storage_check_enabled($cfg, $sid, undef, 1);\n        next if !$ids->{$sid}->{content}->{images};\n        push @$res, $sid;\n    }\n\n    return $res;\n}\n\nsub complete_migration_storage {\n    my ($cmd, $param, $current_value, $all_args) = @_;\n\n    my $targetnode = @$all_args[1];\n\n    my $cfg = PVE::Storage::config();\n    my $ids = $cfg->{ids};\n\n    my $res = [];\n    foreach my $sid (keys %$ids) {\n        next if !PVE::Storage::storage_check_enabled($cfg, $sid, $targetnode, 1);\n        next if !$ids->{$sid}->{content}->{images};\n        push @$res, $sid;\n    }\n\n    return $res;\n}\n\nsub vm_is_paused {\n    my ($vmid, $include_suspended) = @_;\n    my $qmpstatus = eval {\n        PVE::QemuConfig::assert_config_exists_on_node($vmid);\n        mon_cmd($vmid, \"query-status\");\n    };\n    warn \"$@\\n\" if $@;\n    return $qmpstatus\n        && ($qmpstatus->{status} eq \"paused\"\n            || $qmpstatus->{status} eq \"prelaunch\"\n            || ($include_suspended && $qmpstatus->{status} eq \"suspended\"));\n}\n\nsub check_volume_storage_type {\n    my ($storecfg, $vol) = @_;\n\n    my ($storeid, $volname) = PVE::Storage::parse_volume_id($vol);\n    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);\n    my ($vtype) = PVE::Storage::parse_volname($storecfg, $vol);\n\n    die \"storage '$storeid' does not support content-type '$vtype'\\n\"\n        if !$scfg->{content}->{$vtype};\n\n    return 1;\n}\n\n1;\n"
  },
  {
    "path": "src/PVE/VZDump/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nPERLDIR=$(PREFIX)/share/perl5\n\n.PHONY: install\ninstall:\n\tinstall -D -m 0644 QemuServer.pm $(DESTDIR)$(PERLDIR)/PVE/VZDump/QemuServer.pm\n"
  },
  {
    "path": "src/PVE/VZDump/QemuServer.pm",
    "content": "package PVE::VZDump::QemuServer;\n\nuse strict;\nuse warnings;\n\nuse Fcntl qw(:mode);\nuse File::Basename;\nuse File::Path qw(make_path remove_tree);\nuse File::stat qw();\nuse IO::File;\nuse IPC::Open3;\nuse JSON;\nuse POSIX qw(EINTR EAGAIN);\nuse Time::HiRes qw(usleep);\n\nuse PVE::Cluster qw(cfs_read_file);\nuse PVE::INotify;\nuse PVE::IPCC;\nuse PVE::JSONSchema;\nuse PVE::PBSClient;\nuse PVE::RESTEnvironment qw(log_warn);\nuse PVE::QMPClient;\nuse PVE::Storage::Plugin;\nuse PVE::Storage::PBSPlugin;\nuse PVE::Storage;\nuse PVE::Tools qw(run_command);\nuse PVE::VZDump;\nuse PVE::Format qw(render_duration render_bytes);\n\nuse PVE::QemuConfig;\nuse PVE::QemuServer;\nuse PVE::QemuServer::Agent;\nuse PVE::QemuServer::Blockdev;\nuse PVE::QemuServer::Drive qw(checked_volume_format);\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Machine;\nuse PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);\nuse PVE::QemuServer::QMPHelpers;\n\nuse base qw (PVE::VZDump::Plugin);\n\nsub new {\n    my ($class, $vzdump) = @_;\n\n    PVE::VZDump::check_bin('qm');\n\n    my $self = bless { vzdump => $vzdump }, $class;\n\n    $self->{vmlist} = PVE::QemuServer::vzlist();\n    $self->{storecfg} = PVE::Storage::config();\n\n    return $self;\n}\n\nsub type {\n    return 'qemu';\n}\n\nsub vmlist {\n    my ($self) = @_;\n    return [keys %{ $self->{vmlist} }];\n}\n\nsub prepare {\n    my ($self, $task, $vmid, $mode) = @_;\n\n    my $running = PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    if ($running && (my $status = mon_cmd($vmid, 'query-backup'))) {\n        if ($status->{status} && $status->{status} eq 'active') {\n            $self->log('warn', \"left-over backup job still running inside QEMU - canceling now\");\n            mon_cmd($vmid, 'backup-cancel');\n        }\n    }\n\n    $task->{disks} = [];\n\n    my $conf = $self->{vmlist}->{$vmid} = PVE::QemuConfig->load_config($vmid);\n\n    $self->loginfo(\"VM Name: $conf->{name}\")\n        if defined($conf->{name});\n\n    $self->{vm_was_running} = $running ? 1 : 0;\n    $self->{vm_was_paused} = 0;\n    if ($running && PVE::QemuServer::vm_is_paused($vmid, 0)) {\n        # Do not treat a suspended VM as paused, as it would cause us to skip\n        # fs-freeze even if the VM wakes up before we reach qga_fs_freeze.\n        $self->{vm_was_paused} = 1;\n    }\n\n    $task->{hostname} = $conf->{name};\n\n    my $hostname = PVE::INotify::nodename();\n\n    my $vollist = [];\n    my $drivehash = {};\n    my $backup_volumes = PVE::QemuConfig->get_backup_volumes($conf);\n\n    foreach my $volume (@{$backup_volumes}) {\n        my $name = $volume->{key};\n        my $volume_config = $volume->{volume_config};\n        my $volid = $volume_config->{file};\n\n        if (!$volume->{included}) {\n            $self->loginfo(\"exclude disk '$name' '$volid' ($volume->{reason})\");\n            next;\n        } else {\n            my $log = \"include disk '$name' '$volid'\";\n            if (defined(my $size = $volume_config->{size})) {\n                my $readable_size = PVE::JSONSchema::format_size($size);\n                $log .= \" $readable_size\";\n            }\n            $self->loginfo($log);\n        }\n\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n        push @$vollist, $volid if $storeid;\n        $drivehash->{$name} = $volume->{volume_config};\n    }\n\n    PVE::Storage::activate_volumes($self->{storecfg}, $vollist);\n\n    foreach my $ds (sort keys %$drivehash) {\n        my $drive = $drivehash->{$ds};\n\n        my $volid = $drive->{file};\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);\n\n        my $path = $volid;\n        if ($storeid) {\n            $path = PVE::Storage::path($self->{storecfg}, $volid);\n        }\n        next if !$path;\n\n        my ($size, $format);\n        if ($storeid) {\n            # The call in list context can be expensive for certain plugins like RBD, just get size\n            $size = eval { PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5) };\n            die \"cannot determine size of volume '$volid' - $@\\n\" if $@;\n\n            $format = checked_volume_format($self->{storecfg}, $volid);\n        } else {\n            ($size, $format) =\n                eval { PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5); };\n            die \"cannot determine size and format of volume '$volid' - $@\\n\" if $@;\n        }\n\n        my $diskinfo = {\n            path => $path,\n            volid => $volid,\n            storeid => $storeid,\n            size => $size,\n            format => $format,\n            virtdev => $ds,\n            qmdevice => \"drive-$ds\",\n        };\n\n        if ($ds eq 'tpmstate0') {\n            # TPM drive only exists for backup, which is reflected in the name\n            $diskinfo->{qmdevice} = 'drive-tpmstate0-backup';\n            $task->{'tpm-volid'} = $volid;\n        }\n\n        if (-b $path) {\n            $diskinfo->{type} = 'block';\n        } else {\n            $diskinfo->{type} = 'file';\n        }\n\n        push @{ $task->{disks} }, $diskinfo;\n    }\n}\n\nsub vm_status {\n    my ($self, $vmid) = @_;\n\n    my $running = PVE::QemuServer::check_running($vmid) ? 1 : 0;\n\n    return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;\n}\n\nsub lock_vm {\n    my ($self, $vmid) = @_;\n\n    PVE::QemuConfig->set_lock($vmid, 'backup');\n}\n\nsub unlock_vm {\n    my ($self, $vmid) = @_;\n\n    PVE::QemuConfig->remove_lock($vmid, 'backup');\n}\n\nsub stop_vm {\n    my ($self, $task, $vmid) = @_;\n\n    my $opts = $self->{vzdump}->{opts};\n\n    my $wait = $opts->{stopwait} * 60;\n    # send shutdown and wait\n    $self->cmd(\"qm shutdown $vmid --skiplock --keepActive --timeout $wait\");\n}\n\nsub start_vm {\n    my ($self, $task, $vmid) = @_;\n\n    $self->cmd(\"qm start $vmid --skiplock\");\n}\n\nsub suspend_vm {\n    my ($self, $task, $vmid) = @_;\n\n    return if $self->{vm_was_paused};\n\n    $self->cmd(\"qm suspend $vmid --skiplock\");\n}\n\nsub resume_vm {\n    my ($self, $task, $vmid) = @_;\n\n    return if $self->{vm_was_paused};\n\n    $self->cmd(\"qm resume $vmid --skiplock\");\n}\n\nsub assemble {\n    my ($self, $task, $vmid) = @_;\n\n    my $conffile = PVE::QemuConfig->config_file($vmid);\n\n    my $outfile = \"$task->{tmpdir}/qemu-server.conf\";\n    my $firewall_src = \"/etc/pve/firewall/$vmid.fw\";\n    my $firewall_dest = \"$task->{tmpdir}/qemu-server.fw\";\n\n    my $outfd = IO::File->new(\">$outfile\") or die \"unable to open '$outfile' - $!\\n\";\n    my $conffd = IO::File->new($conffile, 'r') or die \"unable to open '$conffile' - $!\\n\";\n\n    my $found_snapshot;\n    my $found_pending;\n    my $found_special;\n    while (defined(my $line = <$conffd>)) {\n        next if $line =~ m/^\\#vzdump\\#/; # just to be sure\n        next if $line =~ m/^\\#qmdump\\#/; # just to be sure\n        if ($line =~ m/^\\[(.*)\\]\\s*$/) {\n            if ($1 =~ m/^PENDING$/i) {\n                $found_pending = 1;\n            } elsif ($1 =~ m/^special:.*$/) {\n                $found_special = 1;\n            } else {\n                $found_snapshot = 1;\n            }\n        }\n        # skip all snapshots, pending changes and special sections\n        next if $found_snapshot || $found_pending || $found_special;\n\n        if ($line =~ m/^unused\\d+:\\s*(\\S+)\\s*/) {\n            $self->loginfo(\"skip unused drive '$1' (not included into backup)\");\n            next;\n        }\n        next if $line =~ m/^lock:/ || $line =~ m/^parent:/;\n\n        print $outfd $line;\n    }\n\n    foreach my $di (@{ $task->{disks} }) {\n        if ($di->{type} eq 'block' || $di->{type} eq 'file') {\n            my $storeid = $di->{storeid} || '';\n            my $format = $di->{format} || '';\n            print $outfd \"#qmdump#map:$di->{virtdev}:$di->{qmdevice}:$storeid:$format:\\n\";\n        } else {\n            die \"internal error\";\n        }\n    }\n\n    if ($found_special) {\n        $self->loginfo(\"special config section found (not included into backup)\");\n    }\n    if ($found_snapshot) {\n        $self->loginfo(\"snapshots found (not included into backup)\");\n    }\n    if ($found_pending) {\n        $self->loginfo(\"pending configuration changes found (not included into backup)\");\n    }\n\n    PVE::Tools::file_copy($firewall_src, $firewall_dest) if -f $firewall_src;\n}\n\nsub archive {\n    my ($self, $task, $vmid, $filename, $comp) = @_;\n\n    my $opts = $self->{vzdump}->{opts};\n    my $scfg = $opts->{scfg};\n\n    if ($self->{vzdump}->{opts}->{pbs}) {\n        $self->archive_pbs($task, $vmid);\n    } elsif ($self->{vzdump}->{'backup-provider'}) {\n        $self->archive_external($task, $vmid);\n    } else {\n        $self->archive_vma($task, $vmid, $filename, $comp);\n    }\n}\n\nmy $bitmap_action_to_human = sub {\n    my ($self, $info) = @_;\n\n    my $action = $info->{action};\n\n    if ($action eq \"not-used\") {\n        return \"disabled (no support)\";\n    } elsif ($action eq \"not-used-removed\") {\n        return \"disabled (old bitmap cleared)\";\n    } elsif ($action eq \"new\") {\n        return \"created new\";\n    } elsif ($action eq \"used\") {\n        if ($info->{dirty} == 0) {\n            return \"OK (drive clean)\";\n        } else {\n            my $size = render_bytes($info->{size}, 1);\n            my $dirty = render_bytes($info->{dirty}, 1);\n            return \"OK ($dirty of $size dirty)\";\n        }\n    } elsif ($action eq \"invalid\") {\n        return \"existing bitmap was invalid and has been cleared\";\n    } elsif ($action eq \"missing-recreated\") {\n        # Lie about the TPM state, because it is newly attached each time.\n        return \"created new\" if $info->{drive} eq 'drive-tpmstate0-backup';\n        return \"expected bitmap was missing and has been recreated\";\n    } else {\n        return \"unknown\";\n    }\n};\n\nmy $query_backup_status_loop = sub {\n    my ($self, $vmid, $job_uuid, $qemu_support) = @_;\n\n    my $starttime = time();\n    my $last_time = $starttime;\n    my ($last_percent, $last_total, $last_target, $last_zero, $last_transferred) =\n        (-1, 0, 0, 0, 0);\n    my ($transferred, $reused);\n\n    my $get_mbps = sub {\n        my ($mb, $delta) = @_;\n        return \"0 B/s\" if $mb <= 0;\n        my $bw = int(($mb / $delta));\n        return render_bytes($bw, 1) . \"/s\";\n    };\n\n    my $target = 0;\n    my $last_reused = 0;\n    my $has_query_bitmap = $qemu_support && $qemu_support->{'query-bitmap-info'};\n    my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});\n    if ($has_query_bitmap) {\n        my $total = 0;\n        my $bitmap_info = mon_cmd($vmid, 'query-pbs-bitmap-info');\n        for my $info (sort { $a->{drive} cmp $b->{drive} } @$bitmap_info) {\n            if (!$is_template) {\n                my $text = $bitmap_action_to_human->($self, $info);\n                my $drive = $info->{drive};\n                $drive =~ s/^drive-//; # for consistency\n                $self->loginfo(\"$drive: dirty-bitmap status: $text\");\n            }\n            $target += $info->{dirty};\n            $total += $info->{size};\n            $last_reused += $info->{size} - $info->{dirty};\n        }\n        if ($target < $total) {\n            my $total_h = render_bytes($total, 1);\n            my $target_h = render_bytes($target, 1);\n            $self->loginfo(\n                \"using fast incremental mode (dirty-bitmap), $target_h dirty of $total_h total\"\n            );\n        }\n    }\n\n    my $last_finishing = 0;\n    while (1) {\n        my $status = mon_cmd($vmid, 'query-backup');\n\n        my $total = $status->{total} || 0;\n        my $dirty = $status->{dirty};\n        $target = (defined($dirty) && $dirty < $total) ? $dirty : $total if !$has_query_bitmap;\n        $transferred = $status->{transferred} || 0;\n        $reused = $status->{reused};\n        my $percent = $target ? int(($transferred * 100) / $target) : 100;\n        my $zero = $status->{'zero-bytes'} || 0;\n\n        die \"got unexpected uuid\\n\" if !$status->{uuid} || ($status->{uuid} ne $job_uuid);\n\n        my $ctime = time();\n        my $duration = $ctime - $starttime;\n\n        my $rbytes = $transferred - $last_transferred;\n        my $wbytes;\n        if ($reused) {\n            # reused includes zero bytes for PBS\n            $wbytes = $rbytes - ($reused - $last_reused);\n        } else {\n            $wbytes = $rbytes - ($zero - $last_zero);\n        }\n\n        my $timediff = ($ctime - $last_time) || 1; # fixme\n        my $mbps_read = $get_mbps->($rbytes, $timediff);\n        my $mbps_write = $get_mbps->($wbytes, $timediff);\n        my $target_h = render_bytes($target, 1);\n        my $transferred_h = render_bytes($transferred, 1);\n\n        my $statusline = sprintf(\n            \"%3d%% ($transferred_h of $target_h) in %s\"\n                . \", read: $mbps_read, write: $mbps_write\",\n            $percent,\n            render_duration($duration),\n        );\n\n        my $res = $status->{status} || 'unknown';\n        if ($res ne 'active') {\n            if ($last_percent < 100) {\n                $self->loginfo($statusline);\n            }\n            if ($res ne 'done') {\n                die(($status->{errmsg} || \"unknown error\") . \"\\n\") if $res eq 'error';\n                die \"got unexpected status '$res'\\n\";\n            }\n            $last_target = $target if $target;\n            $last_total = $total if $total;\n            $last_zero = $zero if $zero;\n            $last_transferred = $transferred if $transferred;\n            last;\n        }\n        if ($percent != $last_percent && ($timediff > 2)) {\n            $self->loginfo($statusline);\n            $last_percent = $percent;\n            $last_target = $target if $target;\n            $last_total = $total if $total;\n            $last_zero = $zero if $zero;\n            $last_transferred = $transferred if $transferred;\n            $last_time = $ctime;\n            $last_reused = $reused;\n\n            if (!$last_finishing && $status->{finishing}) {\n                $self->loginfo(\"Waiting for server to finish backup validation...\");\n            }\n            $last_finishing = $status->{finishing};\n        }\n        sleep(1);\n    }\n\n    my $duration = time() - $starttime;\n\n    if ($last_zero) {\n        my $zero_per = $last_target ? int(($last_zero * 100) / $last_target) : 0;\n        my $zero_h = render_bytes($last_zero);\n        $self->loginfo(\"backup is sparse: $zero_h (${zero_per}%) total zero data\");\n    }\n    if ($reused) {\n        my $reused_h = render_bytes($reused);\n        my $reuse_per = int($reused * 100 / $last_total);\n        $self->loginfo(\"backup was done incrementally, reused $reused_h (${reuse_per}%)\");\n    }\n    if ($transferred) {\n        my $transferred_h = render_bytes($transferred);\n        if ($duration) {\n            my $mbps = $get_mbps->($transferred, $duration);\n            $self->loginfo(\"transferred $transferred_h in $duration seconds ($mbps)\");\n        } else {\n            $self->loginfo(\"transferred $transferred_h in <1 seconds\");\n        }\n    }\n\n    return {\n        total => $last_total,\n        reused => $reused,\n    };\n};\n\nmy $attach_tpmstate_drive = sub {\n    my ($self, $task, $vmid) = @_;\n\n    return if !$task->{'tpm-volid'};\n\n    # unconditionally try to remove the tpmstate-named drive - it only exists\n    # for backing up, and avoids errors if left over from some previous event\n    eval { PVE::QemuServer::Blockdev::detach_tpm_backup_node($vmid); };\n\n    $self->loginfo('attaching TPM drive to QEMU for backup');\n\n    my $drive = { file => $task->{'tpm-volid'}, interface => 'tpmstate', index => 0 };\n    my $extra_options = { 'tpm-backup' => 1, 'read-only' => 1 };\n    PVE::QemuServer::Blockdev::attach($self->{storecfg}, $vmid, $drive, $extra_options);\n};\n\nmy $detach_tpmstate_drive = sub {\n    my ($task, $vmid) = @_;\n\n    return if !$task->{'tpm-volid'} || !PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    eval { PVE::QemuServer::Blockdev::detach_tpm_backup_node($vmid); };\n};\n\nmy sub add_backup_performance_options {\n    my ($qmp_param, $perf, $qemu_support) = @_;\n\n    return if !$perf || scalar(keys $perf->%*) == 0;\n\n    if (!$qemu_support) {\n        my $settings_string = join(', ', sort keys $perf->%*);\n        log_warn(\"ignoring setting(s): $settings_string - issue checking if supported\");\n        return;\n    }\n\n    if (defined($perf->{'max-workers'})) {\n        if ($qemu_support->{'backup-max-workers'}) {\n            $qmp_param->{'max-workers'} = int($perf->{'max-workers'});\n        } else {\n            log_warn(\"ignoring 'max-workers' setting - not supported by running QEMU\");\n        }\n    }\n}\n\nsub get_and_check_pbs_encryption_config {\n    my ($self) = @_;\n\n    my $opts = $self->{vzdump}->{opts};\n    my $scfg = $opts->{scfg};\n\n    my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($scfg, $opts->{storage});\n    my $master_keyfile =\n        PVE::Storage::PBSPlugin::pbs_master_pubkey_file_name($scfg, $opts->{storage});\n\n    if (-e $keyfile) {\n        if (-e $master_keyfile) {\n            $self->loginfo(\"enabling encryption with master key feature\");\n            return ($keyfile, $master_keyfile);\n        } elsif ($scfg->{'master-pubkey'}) {\n            die \"master public key configured but no key file found\\n\";\n        } else {\n            $self->loginfo(\"enabling encryption\");\n            return ($keyfile, undef);\n        }\n    } else {\n        my $encryption_fp = $scfg->{'encryption-key'};\n        die \"encryption configured ('$encryption_fp') but no encryption key file found!\\n\"\n            if $encryption_fp;\n        if (-e $master_keyfile) {\n            $self->log(\n                'warn',\n                \"backup target storage is configured with master-key, but no encryption key set!\"\n                    . \" Ignoring master key settings and creating unencrypted backup.\",\n            );\n        }\n        return (undef, undef);\n    }\n    die\n        \"internal error - unhandled case for getting & checking PBS encryption ($keyfile, $master_keyfile)!\";\n}\n\n# Helper is intended to be called from allocate_fleecing_images() only. Otherwise, fleecing volids\n# have already been recorded in the configuration and PVE::QemuConfig::cleanup_fleecing_images()\n# should be used instead.\nmy sub cleanup_fleecing_images {\n    my ($self, $vmid, $disks) = @_;\n\n    my $failed = [];\n\n    for my $di ($disks->@*) {\n        if (my $volid = $di->{'fleece-volid'}) {\n            eval { PVE::Storage::vdisk_free($self->{storecfg}, $volid); };\n            if (my $err = $@) {\n                $self->log('warn', \"error removing fleecing image '$volid' - $err\");\n                push $failed->@*, $volid;\n            }\n        }\n    }\n\n    PVE::QemuConfig::record_fleecing_images($vmid, $failed);\n}\n\nmy sub allocate_fleecing_images {\n    my ($self, $disks, $vmid, $fleecing_storeid, $format, $all_images) = @_;\n\n    die \"internal error - no fleecing storage specified\\n\" if !$fleecing_storeid;\n\n    my $fleece_volids = [];\n\n    eval {\n        my $n = 0; # counter for fleecing image names\n\n        for my $di ($disks->@*) {\n            # EFI/TPM are usually too small to be worth it, but it's required for external providers\n            next if !$all_images && $di->{virtdev} =~ m/^(?:tpmstate|efidisk)\\d$/;\n            if ($di->{type} eq 'block' || $di->{type} eq 'file') {\n                my $scfg = PVE::Storage::storage_config($self->{storecfg}, $fleecing_storeid);\n                my $name = \"vm-$vmid-fleece-$n\";\n                $name .= \".$format\" if $scfg->{path};\n\n                my $size;\n                if ($format ne 'raw') {\n                    # Since non-raw images cannot be attached with an explicit 'size' parameter to\n                    # QEMU later, pass the exact size to the storage layer. This makes qcow2\n                    # fleecing images work for non-1KiB-aligned source images.\n                    $size = $di->{'block-node-size'} / 1024;\n                } else {\n                    $size = PVE::Tools::convert_size($di->{'block-node-size'}, 'b' => 'kb');\n                }\n\n                $di->{'fleece-volid'} = PVE::Storage::vdisk_alloc(\n                    $self->{storecfg}, $fleecing_storeid, $vmid, $format, $name, $size,\n                );\n\n                push $fleece_volids->@*, $di->{'fleece-volid'};\n\n                $n++;\n            } else {\n                die \"implement me (type '$di->{type}')\";\n            }\n        }\n    };\n    if (my $err = $@) {\n        cleanup_fleecing_images($self, $vmid, $disks);\n        die $err;\n    }\n\n    PVE::QemuConfig::record_fleecing_images($vmid, $fleece_volids);\n}\n\nmy sub detach_fleecing_images {\n    my ($disks, $vmid) = @_;\n\n    return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);\n\n    for my $di ($disks->@*) {\n        if (my $volid = $di->{'fleece-volid'}) {\n            my $node_name = \"$di->{qmdevice}-fleecing\";\n            eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name) };\n        }\n    }\n}\n\nmy sub attach_fleecing_images {\n    my ($self, $disks, $vmid, $format) = @_;\n\n    # unconditionally try to remove potential left-overs from a previous backup\n    detach_fleecing_images($disks, $vmid);\n\n    my $vollist = [map { $_->{'fleece-volid'} } grep { $_->{'fleece-volid'} } $disks->@*];\n    PVE::Storage::activate_volumes($self->{storecfg}, $vollist);\n\n    for my $di ($disks->@*) {\n        if (my $volid = $di->{'fleece-volid'}) {\n            $self->loginfo(\"$di->{qmdevice}: attaching fleecing image $volid to QEMU\");\n\n            my ($interface, $index) = PVE::QemuServer::Drive::parse_drive_interface($di->{virtdev});\n            my $drive = {\n                file => $volid,\n                interface => $interface,\n                index => $index,\n                format => $format,\n                discard => 'on',\n            };\n\n            my $options = { 'fleecing' => 1 };\n            $options->{'tpm-backup'} = 1 if $interface eq 'tpmstate';\n            # Specify size explicitly, to make it work if storage backend rounded up size for\n            # fleecing image when allocating.\n            $options->{size} = $di->{'block-node-size'} if $format eq 'raw';\n\n            PVE::QemuServer::Blockdev::attach($self->{storecfg}, $vmid, $drive, $options);\n        }\n    }\n}\n\nmy sub check_and_prepare_fleecing {\n    my ($self, $vmid, $fleecing_opts, $disks, $is_template, $qemu_support, $all_images) = @_;\n\n    # Even if the VM was started specifically for fleecing, it's possible that the VM is resumed and\n    # then starts doing IO. For VMs that are not resumed the fleecing images will just stay empty,\n    # so there is no big cost.\n\n    my $use_fleecing = $fleecing_opts && $fleecing_opts->{enabled} && !$is_template;\n\n    if ($use_fleecing && !$qemu_support->{'backup-fleecing'}) {\n        $self->log(\n            'warn',\n            \"running QEMU version does not support backup fleecing - continuing without\",\n        );\n        $use_fleecing = 0;\n    }\n\n    # clean up potential left-overs from a previous attempt\n    eval {\n        PVE::QemuConfig::cleanup_fleecing_images(\n            $vmid,\n            $self->{storecfg},\n            sub { $self->log($_[0], $_[1]); },\n        );\n    };\n    $self->log('warn', \"attempt to clean up left-over fleecing images failed - $@\") if $@;\n\n    if ($use_fleecing) {\n        $self->query_block_node_sizes($vmid, $disks);\n\n        my $format = PVE::Storage::resolve_format_hint($self->{storecfg}, $fleecing_opts->{storage},\n            'qcow2');\n\n        allocate_fleecing_images(\n            $self,\n            $disks,\n            $vmid,\n            $fleecing_opts->{storage},\n            $format,\n            $all_images,\n        );\n        attach_fleecing_images($self, $disks, $vmid, $format);\n    }\n\n    return $use_fleecing;\n}\n\nsub archive_pbs {\n    my ($self, $task, $vmid) = @_;\n\n    my $conffile = \"$task->{tmpdir}/qemu-server.conf\";\n    my $firewall = \"$task->{tmpdir}/qemu-server.fw\";\n\n    my $opts = $self->{vzdump}->{opts};\n    my $scfg = $opts->{scfg};\n\n    my $starttime = time();\n\n    my $fingerprint = $scfg->{fingerprint};\n    my $repo = PVE::PBSClient::get_repository($scfg);\n    my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $opts->{storage});\n    my ($keyfile, $master_keyfile) = $self->get_and_check_pbs_encryption_config();\n\n    my $diskcount = scalar(@{ $task->{disks} });\n    # proxmox-backup-client can only handle raw files and block devs, so only use it (directly) for\n    # disk-less VMs\n    if (!$diskcount) {\n        $self->loginfo(\"backup contains no disks\");\n\n        local $ENV{PBS_PASSWORD} = $password;\n        local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);\n        my $cmd = [\n            '/usr/bin/proxmox-backup-client',\n            'backup',\n            '--repository',\n            $repo,\n            '--backup-type',\n            'vm',\n            '--backup-id',\n            \"$vmid\",\n            '--backup-time',\n            $task->{backup_time},\n        ];\n        if (defined(my $ns = $scfg->{namespace})) {\n            push @$cmd, '--ns', $ns;\n        }\n        if (defined($keyfile)) {\n            push @$cmd, '--keyfile', $keyfile;\n            push @$cmd, '--master-pubkey-file', $master_keyfile if defined($master_keyfile);\n        }\n\n        push @$cmd, \"qemu-server.conf:$conffile\";\n        push @$cmd, \"fw.conf:$firewall\" if -e $firewall;\n\n        $self->loginfo(\"starting diskless backup\");\n        $self->loginfo(join(' ', @$cmd));\n\n        $self->cmd($cmd);\n\n        return;\n    }\n\n    # get list early so we die on unknown drive types before doing anything\n    my $devlist = _get_task_devlist($task);\n\n    my $backup_job_uuid;\n    eval {\n        $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {\n            die \"interrupted by signal\\n\";\n        };\n\n        $self->enforce_vm_running_for_backup($vmid);\n        $self->{qmeventd_fh} = PVE::QemuServer::register_qmeventd_handle($vmid);\n\n        my $qemu_support = eval { mon_cmd($vmid, \"query-proxmox-support\") };\n        my $err = $@;\n        if (!$qemu_support || $err) {\n            die \"query-proxmox-support returned empty value\\n\" if !$err;\n            if ($err =~ m/The command query-proxmox-support has not been found/) {\n                die \"PBS backups are not supported by the running QEMU version. Please make \"\n                    . \"sure you've installed the latest version and the VM has been restarted.\\n\";\n            } else {\n                die \"QMP command query-proxmox-support failed - $err\\n\";\n            }\n        }\n\n        # pve-qemu supports it since 5.2.0-1 (PVE 6.4), so safe to die since PVE 8\n        die \"master key configured but running QEMU version does not support master keys\\n\"\n            if !$qemu_support->{'pbs-masterkey'} && defined($master_keyfile);\n\n        $attach_tpmstate_drive->($self, $task, $vmid);\n\n        my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});\n\n        $task->{'use-fleecing'} = check_and_prepare_fleecing(\n            $self, $vmid, $opts->{fleecing}, $task->{disks}, $is_template, $qemu_support, 0,\n        );\n\n        my $fs_frozen = $self->qga_fs_freeze($task, $vmid);\n\n        my $params = {\n            format => \"pbs\",\n            'backup-file' => $repo,\n            'backup-id' => \"$vmid\",\n            'backup-time' => $task->{backup_time},\n            password => $password,\n            devlist => $devlist,\n            'config-file' => $conffile,\n        };\n        $params->{fleecing} = JSON::true if $task->{'use-fleecing'};\n\n        if (defined(my $ns = $scfg->{namespace})) {\n            $params->{'backup-ns'} = $ns;\n        }\n\n        $params->{speed} = $opts->{bwlimit} * 1024 if $opts->{bwlimit};\n        add_backup_performance_options($params, $opts->{performance}, $qemu_support);\n\n        $params->{fingerprint} = $fingerprint if defined($fingerprint);\n        $params->{'firewall-file'} = $firewall if -e $firewall;\n\n        $params->{encrypt} = defined($keyfile) ? JSON::true : JSON::false;\n        if (defined($keyfile)) {\n            $params->{keyfile} = $keyfile;\n            $params->{\"master-keyfile\"} = $master_keyfile if defined($master_keyfile);\n        }\n\n        $params->{'use-dirty-bitmap'} = JSON::true\n            if $qemu_support->{'pbs-dirty-bitmap'} && !$is_template;\n\n        $params->{timeout} = 125; # give some time to connect to the backup server\n\n        $self->loginfo(\"starting backup via QMP command\");\n        my $res = eval { mon_cmd($vmid, \"backup\", %$params) };\n        my $qmperr = $@;\n        $backup_job_uuid = $res->{UUID} if $res;\n\n        if ($fs_frozen) {\n            $self->qga_fs_thaw($vmid);\n        }\n\n        die $qmperr if $qmperr;\n        die \"got no uuid for backup task\\n\" if !defined($backup_job_uuid);\n\n        $self->loginfo(\"started backup task '$backup_job_uuid'\");\n\n        $self->resume_vm_after_job_start($task, $vmid);\n\n        my $stat = $query_backup_status_loop->($self, $vmid, $backup_job_uuid, $qemu_support);\n        $task->{size} = $stat->{total};\n    };\n    my $err = $@;\n    if ($err) {\n        $self->logerr($err);\n        $self->mon_backup_cancel($vmid);\n        $self->resume_vm_after_job_start($task, $vmid);\n    }\n    $self->restore_vm_power_state($vmid);\n\n    die $err if $err;\n}\n\nmy $fork_compressor_pipe = sub {\n    my ($self, $comp, $outfileno) = @_;\n\n    my @pipefd = POSIX::pipe();\n    my $cpid = fork();\n    die \"unable to fork worker - $!\" if !defined($cpid) || $cpid < 0;\n    if ($cpid == 0) {\n        eval {\n            POSIX::close($pipefd[1]);\n            # redirect STDIN\n            my $fd = fileno(STDIN);\n            close STDIN;\n            POSIX::close(0) if $fd != 0;\n            die \"unable to redirect STDIN - $!\"\n                if !open(STDIN, \"<&\", $pipefd[0]);\n\n            # redirect STDOUT\n            $fd = fileno(STDOUT);\n            close STDOUT;\n            POSIX::close(1) if $fd != 1;\n\n            die \"unable to redirect STDOUT - $!\"\n                if !open(STDOUT, \">&\", $outfileno);\n\n            exec($comp);\n            die \"fork compressor '$comp' failed\\n\";\n        };\n        if (my $err = $@) {\n            $self->logerr($err);\n            POSIX::_exit(1);\n        }\n        POSIX::_exit(0);\n        kill(-9, $$);\n    } else {\n        POSIX::close($pipefd[0]);\n        $outfileno = $pipefd[1];\n    }\n\n    return ($cpid, $outfileno);\n};\n\nsub archive_vma {\n    my ($self, $task, $vmid, $filename, $comp) = @_;\n\n    my $conffile = \"$task->{tmpdir}/qemu-server.conf\";\n    my $firewall = \"$task->{tmpdir}/qemu-server.fw\";\n\n    my $opts = $self->{vzdump}->{opts};\n\n    my $starttime = time();\n\n    my $speed = 0;\n    if ($opts->{bwlimit}) {\n        $speed = $opts->{bwlimit} * 1024;\n    }\n\n    my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});\n\n    my $diskcount = scalar(@{ $task->{disks} });\n    if (!$diskcount) {\n        $self->loginfo(\"backup contains no disks\");\n\n        my $outcmd;\n        if ($comp) {\n            $outcmd = \"exec:$comp\";\n        } else {\n            $outcmd = \"exec:cat\";\n        }\n\n        $outcmd .= \" > $filename\" if !$opts->{stdout};\n\n        my $cmd = ['/usr/bin/vma', 'create', '-v', '-c', $conffile];\n        push @$cmd, '-c', $firewall if -e $firewall;\n        push @$cmd, $outcmd;\n\n        $self->loginfo(\"starting diskless backup\");\n        $self->loginfo(join(' ', @$cmd));\n\n        if ($opts->{stdout}) {\n            $self->cmd($cmd, output => \">&\" . fileno($opts->{stdout}));\n        } else {\n            $self->cmd($cmd);\n        }\n\n        return;\n    }\n\n    my $devlist = _get_task_devlist($task);\n\n    my $cpid;\n    my $backup_job_uuid;\n\n    eval {\n        $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {\n            die \"interrupted by signal\\n\";\n        };\n\n        $self->enforce_vm_running_for_backup($vmid);\n        $self->{qmeventd_fh} = PVE::QemuServer::register_qmeventd_handle($vmid);\n\n        # Currently, failing to determine Proxmox support is not critical here, because it's only\n        # used for performance settings like 'max-workers'.\n        my $qemu_support = eval { mon_cmd($vmid, \"query-proxmox-support\") };\n        log_warn($@) if $@;\n\n        $attach_tpmstate_drive->($self, $task, $vmid);\n\n        $task->{'use-fleecing'} = check_and_prepare_fleecing(\n            $self, $vmid, $opts->{fleecing}, $task->{disks}, $is_template, $qemu_support, 0,\n        );\n\n        my $outfh;\n        if ($opts->{stdout}) {\n            $outfh = $opts->{stdout};\n        } else {\n            $outfh = IO::File->new($filename, \"w\")\n                || die \"unable to open file '$filename' - $!\\n\";\n        }\n        my $outfileno = fileno($outfh);\n\n        if ($comp) {\n            ($cpid, $outfileno) = $fork_compressor_pipe->($self, $comp, $outfileno);\n        }\n\n        my $qmpclient = PVE::QMPClient->new();\n        my $qmp_peer = vm_qmp_peer($vmid);\n        my $backup_cb = sub {\n            my ($vmid, $resp) = @_;\n            $backup_job_uuid = $resp->{return}->{UUID};\n        };\n        my $add_fd_cb = sub {\n            my ($vmid, $resp) = @_;\n\n            my $params = {\n                'backup-file' => \"/dev/fdname/backup\",\n                speed => $speed,\n                'config-file' => $conffile,\n                devlist => $devlist,\n            };\n            $params->{'firewall-file'} = $firewall if -e $firewall;\n            $params->{fleecing} = JSON::true if $task->{'use-fleecing'};\n            add_backup_performance_options($params, $opts->{performance}, $qemu_support);\n\n            $qmpclient->queue_cmd($qmp_peer, $backup_cb, 'backup', %$params);\n        };\n\n        $qmpclient->queue_cmd(\n            $qmp_peer, $add_fd_cb, 'getfd',\n            fd => $outfileno,\n            fdname => \"backup\",\n        );\n\n        my $fs_frozen = $self->qga_fs_freeze($task, $vmid);\n\n        $self->loginfo(\"starting backup via QMP command\");\n        eval { $qmpclient->queue_execute(30) };\n        my $qmperr = $@;\n\n        if ($fs_frozen) {\n            $self->qga_fs_thaw($vmid);\n        }\n\n        die \"executing QMP command to start backup failed - $qmperr\" if $qmperr;\n        die $qmpclient->{errors}->{$vmid} if $qmpclient->{errors}->{$vmid};\n\n        if ($cpid) {\n            POSIX::close($outfileno) == 0\n                || die \"close output file handle failed\\n\";\n        }\n\n        die \"got no uuid for backup task\\n\" if !defined($backup_job_uuid);\n\n        $self->loginfo(\"started backup task '$backup_job_uuid'\");\n\n        $self->resume_vm_after_job_start($task, $vmid);\n\n        $query_backup_status_loop->($self, $vmid, $backup_job_uuid);\n    };\n    my $err = $@;\n    if ($err) {\n        $self->logerr($err);\n        $self->mon_backup_cancel($vmid);\n        $self->resume_vm_after_job_start($task, $vmid);\n    }\n\n    $self->restore_vm_power_state($vmid);\n\n    if ($err) {\n        if ($cpid) {\n            kill(9, $cpid);\n            waitpid($cpid, 0);\n        }\n        die $err;\n    }\n\n    if ($cpid && (waitpid($cpid, 0) > 0)) {\n        my $stat = $?;\n        my $ec = $stat >> 8;\n        my $signal = $stat & 127;\n        if ($ec || $signal) {\n            die \"$comp failed - wrong exit status $ec\" . ($signal ? \" (signal $signal)\\n\" : \"\\n\");\n        }\n    }\n}\n\nsub _get_task_devlist {\n    my ($task) = @_;\n\n    my $devlist = '';\n    foreach my $di (@{ $task->{disks} }) {\n        if ($di->{type} eq 'block' || $di->{type} eq 'file') {\n            $devlist .= ',' if $devlist;\n            $devlist .= $di->{qmdevice};\n        } else {\n            die \"implement me (type '$di->{type}')\";\n        }\n    }\n    return $devlist;\n}\n\nsub qga_fs_freeze {\n    my ($self, $task, $vmid) = @_;\n    return\n        if $task->{mode} eq 'stop'\n        || !$self->{vm_was_running}\n        || $self->{vm_was_paused};\n\n    my $conf = $self->{vmlist}->{$vmid};\n\n    if (!PVE::QemuServer::Agent::get_qga_key($conf, 'enabled')) {\n        $self->loginfo(\"skipping guest-agent 'fs-freeze', agent not configured in VM options\");\n        return;\n    }\n\n    if (!PVE::QemuServer::Agent::qga_check_running($vmid, 1)) {\n        $self->loginfo(\"skipping guest-agent 'fs-freeze', agent configured but not running?\");\n        return;\n    }\n\n    if (!PVE::QemuServer::Agent::should_fs_freeze($conf)) {\n        $self->loginfo(\"skipping guest-agent 'fs-freeze', disabled in VM options\");\n        return;\n    }\n\n    $self->loginfo(\"issuing guest-agent 'fs-freeze' command\");\n    eval { PVE::QemuServer::Agent::guest_fsfreeze($vmid); };\n    $self->logerr($@) if $@;\n\n    return 1; # even on error, ensure we always thaw again\n}\n\n# only call if fs_freeze return 1\nsub qga_fs_thaw {\n    my ($self, $vmid) = @_;\n\n    $self->loginfo(\"issuing guest-agent 'fs-thaw' command\");\n    eval { PVE::QemuServer::Agent::guest_fsthaw($vmid); };\n    $self->logerr($@) if $@;\n}\n\n# The size for fleecing images needs to be exactly the same size as QEMU sees. E.g. EFI disk can bex\n# attached with a smaller size then the underyling image on the storage.\nsub query_block_node_sizes {\n    my ($self, $vmid, $disks) = @_;\n\n    my $block_info = PVE::QemuServer::Blockdev::get_block_info($vmid);\n\n    for my $diskinfo ($disks->@*) {\n        my $drive_key = $diskinfo->{virtdev};\n\n        my $block_node_size;\n        if ($drive_key eq 'tpmstate0') {\n            # There is no front-end device for TPM state, so it's not included in the result of\n            # get_block_info(). Note that it is always attached with the same explicit node name.\n            my $named_block_node_info = mon_cmd($vmid, 'query-named-block-nodes');\n            for my $info ($named_block_node_info->@*) {\n                next if $info->{'node-name'} ne 'drive-tpmstate0-backup';\n                $block_node_size = $info->{image}->{'virtual-size'};\n                last;\n            }\n        } else {\n            $block_node_size =\n                eval { $block_info->{$drive_key}->{inserted}->{image}->{'virtual-size'}; };\n        }\n\n        if (!$block_node_size) {\n            $self->loginfo(\n                \"could not determine block node size of drive '$drive_key' - using fallback\");\n            $block_node_size = $diskinfo->{size}\n                or die \"could not determine size of drive '$drive_key'\\n\";\n        }\n        $diskinfo->{'block-node-size'} = $block_node_size;\n    }\n\n    return;\n}\n\n# we need a running QEMU/KVM process for backup, starts a paused (prelaunch)\n# one if VM isn't already running\nsub enforce_vm_running_for_backup {\n    my ($self, $vmid) = @_;\n\n    if (PVE::QemuServer::check_running($vmid)) {\n        $self->{vm_was_running} = 1;\n        return;\n    }\n\n    eval {\n        $self->loginfo(\"starting kvm to execute backup task\");\n        # start with skiplock\n        my $params = {\n            skiplock => 1,\n            skiptemplate => 1,\n            paused => 1,\n        };\n        PVE::QemuServer::vm_start($self->{storecfg}, $vmid, $params);\n    };\n    die $@ if $@;\n}\n\n# resume VM again once in a clear state (stop mode backup of running VM)\nsub resume_vm_after_job_start {\n    my ($self, $task, $vmid) = @_;\n\n    return if !$self->{vm_was_running} || $self->{vm_was_paused};\n\n    if (my $stoptime = $task->{vmstoptime}) {\n        my $delay = time() - $task->{vmstoptime};\n        $task->{vmstoptime} = undef; # avoid printing 'online after ..' twice\n        $self->loginfo(\"resuming VM again after $delay seconds\");\n    } else {\n        $self->loginfo(\"resuming VM again\");\n    }\n    mon_cmd($vmid, 'cont', timeout => 45);\n}\n\n# stop again if VM was not running before\nsub restore_vm_power_state {\n    my ($self, $vmid) = @_;\n\n    # we always let VMs keep running\n    return if $self->{vm_was_running};\n\n    eval {\n        my $resp = mon_cmd($vmid, 'query-status');\n        my $status = $resp && $resp->{status} ? $resp->{status} : 'unknown';\n        if ($status eq 'prelaunch') {\n            $self->loginfo(\"stopping kvm after backup task\");\n            PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1);\n        } else {\n            $self->loginfo(\"kvm status changed after backup ('$status') - keep VM running\");\n        }\n    };\n    warn $@ if $@;\n}\n\nsub mon_backup_cancel {\n    my ($self, $vmid) = @_;\n\n    $self->loginfo(\"aborting backup job\");\n    eval { mon_cmd($vmid, 'backup-cancel') };\n    $self->logerr($@) if $@;\n}\n\nsub snapshot {\n    my ($self, $task, $vmid) = @_;\n\n    # nothing to do\n}\n\nmy sub cleanup_file_handles {\n    my ($self, $file_handles) = @_;\n\n    for my $file_handle ($file_handles->@*) {\n        close($file_handle) or $self->log('warn', \"unable to close file handle - $!\");\n    }\n}\n\nmy sub cleanup_nbd_mounts {\n    my ($self, $info) = @_;\n\n    for my $mount_point (keys $info->%*) {\n        my $pid_file = delete($info->{$mount_point}->{'pid-file'});\n        unlink($pid_file) or $self->log('warn', \"unable to unlink '$pid_file' - $!\");\n        # Do a lazy unmount, because the target might still be busy even if the file handle was\n        # already closed.\n        eval { run_command(['fusermount', '-z', '-u', $mount_point]); };\n        if (my $err = $@) {\n            delete $info->{$mount_point};\n            $self->log('warn', \"unable to unmount NBD backup source '$mount_point' - $err\");\n        }\n    }\n\n    # Wait for the unmount before cleaning up child PIDs to avoid 'nbdfuse' processes being\n    # interrupted by the signals issued there.\n    my $waited;\n    my $wait_limit = 50; # 5 seconds\n    for ($waited = 0; $waited < $wait_limit && scalar(keys $info->%*); $waited++) {\n        for my $mount_point (keys $info->%*) {\n            delete($info->{$mount_point}) if !-e $info->{$mount_point}->{'virtual-file'};\n            eval { remove_tree($mount_point); };\n        }\n        usleep(100_000);\n    }\n    # just informational, remaining child processes will be killed afterwards\n    $self->loginfo(\"unable to gracefully cleanup NBD fuse mounts\") if scalar(keys $info->%*) != 0;\n}\n\nmy sub cleanup_child_processes {\n    my ($self, $cpids) = @_;\n\n    my $waited;\n    my $wait_limit = 5;\n    for ($waited = 0; $waited < $wait_limit && scalar(keys $cpids->%*); $waited++) {\n        for my $cpid (keys $cpids->%*) {\n            delete($cpids->{$cpid}) if waitpid($cpid, POSIX::WNOHANG) > 0;\n        }\n        if ($waited == 0) {\n            kill 15, $_ for keys $cpids->%*;\n        }\n        sleep 1;\n    }\n    if ($waited == $wait_limit && scalar(keys $cpids->%*)) {\n        kill 9, $_ for keys $cpids->%*;\n        sleep 1;\n        for my $cpid (keys $cpids->%*) {\n            delete($cpids->{$cpid}) if waitpid($cpid, POSIX::WNOHANG) > 0;\n        }\n        $self->log('warn', \"unable to collect child process '$_'\") for keys $cpids->%*;\n    }\n}\n\nsub cleanup {\n    my ($self, $task, $vmid) = @_;\n\n    # If VM was started only for backup, it is already stopped now.\n    if (PVE::QemuServer::Helpers::vm_running_locally($vmid)) {\n        if ($task->{cleanup}->{'nbd-stop'}) {\n            eval { PVE::QemuServer::QMPHelpers::nbd_stop($vmid); };\n            $self->logerr($@) if $@;\n        }\n\n        if (my $info = $task->{cleanup}->{'backup-access-teardown'}) {\n            my $params = {\n                'target-id' => $info->{'target-id'},\n                timeout => 60,\n                success => $info->{success} ? JSON::true : JSON::false,\n            };\n\n            $self->loginfo(\"tearing down backup-access\");\n            eval { mon_cmd($vmid, \"backup-access-teardown\", $params->%*) };\n            $self->logerr($@) if $@;\n        }\n\n        $detach_tpmstate_drive->($task, $vmid);\n    }\n\n    if ($task->{'use-fleecing'}) {\n        eval {\n            detach_fleecing_images($task->{disks}, $vmid);\n            PVE::QemuConfig::cleanup_fleecing_images(\n                $vmid,\n                $self->{storecfg},\n                sub { $self->log($_[0], $_[1]); },\n            );\n        };\n        $self->log('warn', \"attempt to clean up fleecing images failed - $@\") if $@;\n    }\n\n    if ($self->{qmeventd_fh}) {\n        close($self->{qmeventd_fh});\n    }\n\n    cleanup_file_handles($self, $task->{cleanup}->{'file-handles'})\n        if $task->{cleanup}->{'file-handles'};\n\n    cleanup_nbd_mounts($self, $task->{cleanup}->{'nbd-mounts'})\n        if $task->{cleanup}->{'nbd-mounts'};\n\n    cleanup_child_processes($self, $task->{cleanup}->{'child-pids'})\n        if $task->{cleanup}->{'child-pids'};\n\n    if (my $dir = $task->{'backup-access-root-dir'}) {\n        eval { remove_tree($dir) };\n        $self->log('warn', \"unable to cleanup directory $dir - $@\") if $@;\n    }\n}\n\nmy sub virtual_file_backup_prepare {\n    my ($self, $vmid, $task, $device_name, $size, $nbd_path, $bitmap_name) = @_;\n\n    my $cleanup = $task->{cleanup};\n\n    my $nbd_uri = \"nbd+unix:///${device_name}?socket=${nbd_path}\";\n\n    my $error_fh;\n    my $next_dirty_region;\n\n    # If there is no dirty bitmap, it can be treated as if there's a full dirty one. The output of\n    # nbdinfo is a list of tuples with offset, length, type, description. The first bit of 'type' is\n    # set when the bitmap is dirty, see QEMU's docs/interop/nbd.txt\n    my $dirty_bitmap = [];\n    if ($bitmap_name) {\n        my $input = IO::File->new();\n        my $info = IO::File->new();\n        $error_fh = IO::File->new();\n        my $nbdinfo_cmd = [\"nbdinfo\", $nbd_uri, \"--map=qemu:dirty-bitmap:${bitmap_name}\"];\n        my $cpid = open3($input, $info, $error_fh, $nbdinfo_cmd->@*)\n            or die \"failed to spawn nbdinfo child - $!\\n\";\n        $cleanup->{'child-pids'}->{$cpid} = 1;\n\n        $next_dirty_region = sub {\n            my ($offset, $length, $type);\n            do {\n                my $line = <$info>;\n                return if !$line;\n                die \"unexpected output from nbdinfo - $line\\n\"\n                    if $line !~ m/^\\s*(\\d+)\\s*(\\d+)\\s*(\\d+)/; # also untaints\n                ($offset, $length, $type) = ($1, $2, $3);\n            } while (($type & 0x1) == 0); # not dirty\n            return ($offset, $length);\n        };\n    } else {\n        my $done = 0;\n        $next_dirty_region = sub {\n            return if $done;\n            $done = 1;\n            return (0, $size);\n        };\n    }\n\n    my $mount_point =\n        $task->{'backup-access-root-dir'} . \"/${vmid}-nbd.backup-access.${device_name}.$$\";\n    make_path($mount_point) or die \"unable to create directory $mount_point\\n\";\n    $cleanup->{'nbd-mounts'}->{$mount_point} = {};\n\n    # Note that nbdfuse requires \"$dir/$file\". A single name would be treated as a dir and the file\n    # would be named \"$dir/nbd\" then\n    my $virtual_file = \"${mount_point}/${device_name}\";\n    $cleanup->{'nbd-mounts'}->{$mount_point}->{'virtual-file'} = $virtual_file;\n\n    my $pid_file = \"${mount_point}.pid\";\n    PVE::Tools::file_set_contents($pid_file, '', 0600);\n    $cleanup->{'nbd-mounts'}->{$mount_point}->{'pid-file'} = $pid_file;\n\n    my $cpid = fork() // die \"fork failed: $!\\n\";\n    if (!$cpid) {\n        # By default, access will be restricted to the current user, because the allow_other fuse\n        # mount option is not used.\n        eval {\n            run_command(\n                [\"nbdfuse\", '--pidfile', $pid_file, $virtual_file, $nbd_uri],\n                logfunc => sub { $self->loginfo(\"nbdfuse '$virtual_file': $_[0]\") },\n            );\n        };\n        if (my $err = $@) {\n            eval { $self->loginfo($err); };\n            POSIX::_exit(1);\n        }\n        POSIX::_exit(0);\n    }\n    $cleanup->{'child-pids'}->{$cpid} = 1;\n\n    my ($virtual_file_ready, $waited) = (0, 0);\n    while (!$virtual_file_ready && $waited < 30) { # 3 seconds\n        my $pid = PVE::Tools::file_read_firstline($pid_file);\n        if ($pid) {\n            $virtual_file_ready = 1;\n        } else {\n            usleep(100_000);\n            $waited++;\n        }\n    }\n    die \"timeout setting up virtual file '$virtual_file'\" if !$virtual_file_ready;\n\n    $self->loginfo(\"provided NBD export as a virtual file '$virtual_file'\");\n\n    # NOTE O_DIRECT, because each block should be read exactly once and also because fuse will try\n    # to read ahead otherwise, which would produce warning messages if the next block is not\n    # mapped/allocated for the NBD export in case of incremental backup. Open as writable to support\n    # discard.\n    my $fh = IO::File->new($virtual_file, O_RDWR | O_DIRECT)\n        or die \"unable to open backup source '$virtual_file' - $!\\n\";\n    push $cleanup->{'file-handles'}->@*, $fh;\n\n    return ($fh, $next_dirty_region);\n}\n\nmy sub backup_access_to_volume_info {\n    my ($self, $vmid, $task, $backup_access_info, $mechanism, $nbd_path) = @_;\n\n    my $bitmap_action_to_status = {\n        'not-used' => 'none',\n        'not-used-removed' => 'none',\n        'new' => 'new',\n        'used' => 'reuse',\n        'invalid' => 'new',\n        'missing-recreated' => 'new',\n    };\n\n    my $volumes = {};\n\n    for my $info ($backup_access_info->@*) {\n        my $bitmap_status = 'none';\n        my $bitmap_name;\n        if (my $bitmap_action = $info->{'bitmap-action'}) {\n            $bitmap_status = $bitmap_action_to_status->{$bitmap_action}\n                or die \"got unexpected bitmap action '$bitmap_action'\\n\";\n\n            $bitmap_name = $info->{'bitmap-name'} or die \"bitmap-name is not present\\n\";\n        }\n\n        my ($device, $size) = $info->@{qw(device size)};\n\n        $volumes->{$device}->{'bitmap-mode'} = $bitmap_status;\n        $volumes->{$device}->{size} = $size;\n\n        if ($mechanism eq 'file-handle') {\n            my ($fh, $next_dirty_region) = virtual_file_backup_prepare(\n                $self, $vmid, $task, $device, $size, $nbd_path, $bitmap_name,\n            );\n            $volumes->{$device}->{'file-handle'} = $fh;\n            $volumes->{$device}->{'next-dirty-region'} = $next_dirty_region;\n        } elsif ($mechanism eq 'nbd') {\n            $volumes->{$device}->{'nbd-path'} = $nbd_path;\n            $volumes->{$device}->{'bitmap-name'} = $bitmap_name;\n        } else {\n            die \"internal error - unknown mechanism '$mechanism'\";\n        }\n    }\n\n    return $volumes;\n}\n\nsub archive_external {\n    my ($self, $task, $vmid) = @_;\n\n    $task->{'backup-access-root-dir'} = \"/run/qemu-server/${vmid}.backup-access.$$/\";\n    make_path($task->{'backup-access-root-dir'})\n        or die \"unable to create directory $task->{'backup-access-root-dir'}\\n\";\n    chmod(0700, $task->{'backup-access-root-dir'})\n        or die \"unable to chmod directory $task->{'backup-access-root-dir'}\\n\";\n\n    my $guest_config = PVE::Tools::file_get_contents(\"$task->{tmpdir}/qemu-server.conf\");\n    my $firewall_file = \"$task->{tmpdir}/qemu-server.fw\";\n\n    my $opts = $self->{vzdump}->{opts};\n\n    my $backup_provider = $self->{vzdump}->{'backup-provider'};\n\n    $self->loginfo(\"starting external backup via \" . $backup_provider->provider_name());\n\n    my $starttime = time();\n\n    $self->enforce_vm_running_for_backup($vmid);\n    $self->{qmeventd_fh} = PVE::QemuServer::register_qmeventd_handle($vmid);\n\n    eval {\n        $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {\n            die \"interrupted by signal\\n\";\n        };\n\n        my $qemu_support = mon_cmd($vmid, \"query-proxmox-support\");\n\n        if (!$qemu_support->{'backup-access-api'}) {\n            die \"backups access API required for external provider backup is not supported by\"\n                . \" the running QEMU version. Please make sure you've installed the latest \"\n                . \" version and the VM has been restarted.\\n\";\n        }\n\n        $attach_tpmstate_drive->($self, $task, $vmid);\n\n        my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});\n\n        my $fleecing = check_and_prepare_fleecing(\n            $self, $vmid, $opts->{fleecing}, $task->{disks}, $is_template, $qemu_support, 1,\n        );\n        die \"cannot setup backup access without fleecing\\n\" if !$fleecing;\n\n        $task->{'use-fleecing'} = 1;\n\n        my $target_id = \"snapshot-access:$opts->{storage}\";\n\n        my $mechanism = $backup_provider->backup_get_mechanism($vmid, 'qemu');\n        die \"mechanism '$mechanism' requested by backup provider is not supported for VMs\\n\"\n            if $mechanism ne 'file-handle' && $mechanism ne 'nbd';\n\n        $self->loginfo(\"using backup mechanism '$mechanism'\");\n\n        if ($mechanism eq 'file-handle') {\n            # For mechanism 'file-handle', the nbdfuse binary is required. Also, the bitmap needs\n            # to be passed to the provider. The bitmap cannot be dumped via QMP and doing it via\n            # qemu-img is experimental, so use nbdinfo. Both are in libnbd-bin.\n            die \"need 'nbdfuse' binary from package libnbd-bin\\n\" if !-e \"/usr/bin/nbdfuse\";\n        }\n\n        my $devices = {};\n        for my $di ($task->{disks}->@*) {\n            my $device_name = $di->{qmdevice};\n            die \"implement me (type '$di->{type}')\"\n                if $di->{type} ne 'block' && $di->{type} ne 'file';\n            $devices->{$device_name}->{size} = $di->{'block-node-size'};\n        }\n\n        my $incremental_info = $backup_provider->backup_vm_query_incremental($vmid, $devices);\n\n        my $qmp_devices = [];\n        for my $device (sort keys $devices->%*) {\n            my $qmp_device = { device => $device };\n            if (defined(my $mode = $incremental_info->{$device})) {\n                if ($mode eq 'new' || $mode eq 'use' || $mode eq 'none') {\n                    $qmp_device->{'bitmap-mode'} = $mode;\n                } else {\n                    die \"invalid incremental mode '$mode' returned by backup provider plugin\\n\";\n                }\n            }\n            push($qmp_devices->@*, $qmp_device);\n        }\n\n        my $params = {\n            'target-id' => $target_id,\n            devices => $qmp_devices,\n            timeout => 60,\n        };\n\n        my $fs_frozen = $self->qga_fs_freeze($task, $vmid);\n\n        $self->loginfo(\"setting up snapshot-access for backup\");\n\n        $task->{cleanup}->{'backup-access-teardown'} =\n            { 'target-id' => $target_id, success => 0 };\n\n        my $backup_access_info = eval { mon_cmd($vmid, \"backup-access-setup\", $params->%*) };\n        my $qmperr = $@;\n\n        if ($fs_frozen) {\n            $self->qga_fs_thaw($vmid);\n        }\n\n        die $qmperr if $qmperr;\n\n        $self->resume_vm_after_job_start($task, $vmid);\n\n        my $bitmap_info = mon_cmd($vmid, 'query-pbs-bitmap-info');\n        for my $info (sort { $a->{drive} cmp $b->{drive} } $bitmap_info->@*) {\n            my $text = $bitmap_action_to_human->($self, $info);\n            my $drive = $info->{drive};\n            $drive =~ s/^drive-//; # for consistency\n            $self->loginfo(\"$drive: dirty-bitmap status: $text\");\n        }\n\n        $self->loginfo(\"starting NBD server\");\n\n        my $nbd_path = \"$task->{'backup-access-root-dir'}/${vmid}-nbd.backup-access\";\n        mon_cmd(\n            $vmid,\n            \"nbd-server-start\",\n            addr => { type => 'unix', data => { path => $nbd_path } },\n        );\n        $task->{cleanup}->{'nbd-stop'} = 1;\n\n        for my $info ($backup_access_info->@*) {\n            $self->loginfo(\"adding NBD export for $info->{device}\");\n\n            my $export_params = {\n                id => $info->{device},\n                'node-name' => $info->{'node-name'},\n                writable => JSON::true, # for discard\n                type => \"nbd\",\n                name => $info->{device}, # NBD export name\n            };\n\n            if ($info->{'bitmap-name'}) {\n                $export_params->{bitmaps} = [{\n                    node => $info->{'bitmap-node-name'},\n                    name => $info->{'bitmap-name'},\n                }];\n            }\n\n            mon_cmd($vmid, \"block-export-add\", $export_params->%*);\n        }\n\n        my $volumes = backup_access_to_volume_info(\n            $self, $vmid, $task, $backup_access_info, $mechanism, $nbd_path,\n        );\n\n        my $param = {};\n        $param->{'bandwidth-limit'} = $opts->{bwlimit} * 1024 if $opts->{bwlimit};\n        $param->{'firewall-config'} = PVE::Tools::file_get_contents($firewall_file)\n            if -e $firewall_file;\n\n        $backup_provider->backup_vm($vmid, $guest_config, $volumes, $param);\n    };\n    my $err = $@;\n\n    if ($err) {\n        $self->logerr($err);\n        $self->resume_vm_after_job_start($task, $vmid);\n    } else {\n        $task->{cleanup}->{'backup-access-teardown'}->{success} = 1;\n    }\n    $self->restore_vm_power_state($vmid);\n\n    die $err if $err;\n}\n\n1;\n"
  },
  {
    "path": "src/bin/Makefile",
    "content": "PACKAGE ?= qemu-server\n\nDESTDIR=\nPREFIX=/usr\nSBINDIR=$(PREFIX)/sbin\nLIBDIR=$(PREFIX)/lib/$(PACKAGE)\nMANDIR=$(PREFIX)/share/man\nMAN1DIR=$(MANDIR)/man1/\nMAN5DIR=$(MANDIR)/man5/\nBASHCOMPLDIR=$(PREFIX)/share/bash-completion/completions/\nZSHCOMPLDIR=$(PREFIX)/share/zsh/vendor-completions/\n\nPERL_DOC_INC_DIRS=..\n-include /usr/share/pve-doc-generator/pve-doc-generator.mk\n\nall:\n\n\nqm.bash-completion:\n\tPVE_GENERATING_DOCS=1 perl -I.. -T -e \"use PVE::CLI::qm; PVE::CLI::qm->generate_bash_completions();\" >$@.tmp\n\tmv $@.tmp $@\n\nqmrestore.bash-completion:\n\tPVE_GENERATING_DOCS=1 perl -I.. -T -e \"use PVE::CLI::qmrestore; PVE::CLI::qmrestore->generate_bash_completions();\" >$@.tmp\n\tmv $@.tmp $@\n\nqm.zsh-completion:\n\tPVE_GENERATING_DOCS=1 perl -I.. -T -e \"use PVE::CLI::qm; PVE::CLI::qm->generate_zsh_completions();\" >$@.tmp\n\tmv $@.tmp $@\n\nqmrestore.zsh-completion:\n\tPVE_GENERATING_DOCS=1 perl -I.. -T -e \"use PVE::CLI::qmrestore; PVE::CLI::qmrestore->generate_zsh_completions();\" >$@.tmp\n\tmv $@.tmp $@\n\nPKGSOURCES=qm qm.1 qmrestore qmrestore.1 qmextract qm.conf.5 qm.bash-completion qmrestore.bash-completion \\\n\t    qm.zsh-completion qmrestore.zsh-completion cpu-models.conf.5\n\n.PHONY: install\ninstall: $(PKGSOURCES)\n\tinstall -d $(DESTDIR)/$(SBINDIR)\n\tinstall -d $(DESTDIR)$(LIBDIR)\n\tinstall -d $(DESTDIR)/$(MAN1DIR)\n\tinstall -d $(DESTDIR)/$(MAN5DIR)\n\tinstall -d $(DESTDIR)/usr/share/$(PACKAGE)\n\tinstall -m 0644 -D qm.bash-completion $(DESTDIR)/$(BASHCOMPLDIR)/qm\n\tinstall -m 0644 -D qmrestore.bash-completion $(DESTDIR)/$(BASHCOMPLDIR)/qmrestore\n\tinstall -m 0644 -D qm.zsh-completion $(DESTDIR)/$(ZSHCOMPLDIR)/_qm\n\tinstall -m 0644 -D qmrestore.zsh-completion $(DESTDIR)/$(ZSHCOMPLDIR)/_qmrestore\n\tinstall -m 0755 qm $(DESTDIR)$(SBINDIR)\n\tinstall -m 0755 qmrestore $(DESTDIR)$(SBINDIR)\n\tinstall -m 0755 qmextract $(DESTDIR)$(LIBDIR)\n\tinstall -m 0644 qm.1 $(DESTDIR)/$(MAN1DIR)\n\tinstall -m 0644 qmrestore.1 $(DESTDIR)/$(MAN1DIR)\n\tinstall -m 0644 cpu-models.conf.5 $(DESTDIR)/$(MAN5DIR)\n\tinstall -m 0644 qm.conf.5 $(DESTDIR)/$(MAN5DIR)\n\tcd $(DESTDIR)/$(MAN5DIR); ln -s -f qm.conf.5.gz vm.conf.5.gz\n\n.PHONY: test\ntest:\n\tPVE_GENERATING_DOCS=1 perl -I.. ./qm verifyapi\n\n.PHONY: clean\nclean:\n\t$(MAKE) cleanup-docgen\n\n.PHONY: distclean\ndistclean: clean\n"
  },
  {
    "path": "src/bin/qm",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse PVE::CLI::qm;\n\nPVE::CLI::qm->run_cli_handler();\n"
  },
  {
    "path": "src/bin/qmextract",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nuse Getopt::Long;\nuse File::Path;\nuse IO::File;\nuse PVE::INotify;\nuse PVE::JSONSchema;\nuse PVE::Tools;\nuse PVE::Cluster;\nuse PVE::RPCEnvironment;\nuse PVE::Storage;\nuse PVE::QemuServer;\n\n$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';\n\ndie \"please run as root\\n\" if $> != 0;\n\nmy @std_opts = ('storage=s', 'pool=s', 'info', 'prealloc');\n\nsub print_usage {\n    print STDERR\n        \"usage: $0 [--storage=<storeid>] [--pool=<poolid>] [--info] [--prealloc] <archive> <vmid>\\n\\n\";\n}\n\nmy $opts = {};\nif (!GetOptions($opts, @std_opts)) {\n    print_usage();\n    exit(-1);\n}\n\nPVE::INotify::inotify_init();\n\nmy $rpcenv = PVE::RPCEnvironment->init('cli');\n\n$rpcenv->init_request();\n$rpcenv->set_language($ENV{LANG});\n$rpcenv->set_user('root@pam');\n\nsub extract_archive {\n    # NOTE: this is run as tar subprocess (--to-command)\n\n    $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {\n        die \"interrupted by signal\\n\";\n    };\n\n    my $filename = $ENV{TAR_FILENAME};\n    die \"got strange environment -  no TAR_FILENAME\\n\" if !$filename;\n\n    my $filesize = $ENV{TAR_SIZE};\n    die \"got strange file size '$filesize'\\n\" if !$filesize;\n\n    my $tmpdir = $ENV{VZDUMP_TMPDIR};\n    die \"got strange environment -  no VZDUMP_TMPDIR\\n\" if !$tmpdir;\n\n    my $filetype = $ENV{TAR_FILETYPE} || 'none';\n    die \"got strange filetype '$filetype'\\n\" if $filetype ne 'f';\n\n    my $vmid = $ENV{VZDUMP_VMID};\n    PVE::JSONSchema::pve_verify_vmid($vmid);\n\n    my $user = $ENV{VZDUMP_USER};\n    $rpcenv->check_user_enabled($user);\n\n    if ($opts->{info}) {\n        print STDERR \"reading archive member '$filename'\\n\";\n    } else {\n        print STDERR \"extracting '$filename' from archive\\n\";\n    }\n\n    my $conffile = \"$tmpdir/qemu-server.conf\";\n    my $statfile = \"$tmpdir/qmrestore.stat\";\n\n    if ($filename eq 'qemu-server.conf') {\n        my $outfd = IO::File->new($conffile, \"w\")\n            || die \"unable to write file '$conffile'\\n\";\n\n        while (defined(my $line = <>)) {\n            print $outfd $line;\n            print STDERR \"CONFIG: $line\" if $opts->{info};\n        }\n\n        $outfd->close();\n\n        exit(0);\n    }\n\n    if ($opts->{info}) {\n        exec 'dd', 'bs=256K', \"of=/dev/null\";\n        die \"couldn't exec dd: $!\\n\";\n    }\n\n    my $conffd = IO::File->new($conffile, \"r\")\n        || die \"unable to read file '$conffile'\\n\";\n\n    my $map;\n    while (defined(my $line = <$conffd>)) {\n        if ($line =~ m/^\\#vzdump\\#map:(\\S+):(\\S+):(\\d+):(\\S*):$/) {\n            $map->{$2} = { virtdev => $1, size => $3, storeid => $4 };\n        }\n    }\n    close($conffd);\n\n    my $statfd = IO::File->new($statfile, \"a\")\n        || die \"unable to open file '$statfile'\\n\";\n\n    if ($filename !~ m/^.*\\.([^\\.]+)$/) {\n        die \"got strange filename '$filename'\\n\";\n    }\n    my $format = $1;\n\n    my $path;\n\n    if (!$map) {\n        print STDERR \"restoring old style vzdump archive - \" . \"no device map inside archive\\n\";\n        die \"can't restore old style archive to storage '$opts->{storage}'\\n\"\n            if defined($opts->{storage}) && $opts->{storage} ne 'local';\n\n        my $dir = \"/var/lib/vz/images/$vmid\";\n        mkpath $dir;\n\n        $path = \"$dir/$filename\";\n\n        print $statfd \"vzdump::$path\\n\";\n        $statfd->close();\n\n    } else {\n\n        my $info = $map->{$filename};\n        die \"no vzdump info for '$filename'\\n\" if !$info;\n\n        if ($filename !~ m/^vm-disk-$info->{virtdev}\\.([^\\.]+)$/) {\n            die \"got strange filename '$filename'\\n\";\n        }\n\n        if ($filesize != $info->{size}) {\n            die \"detected size difference for '$filename' \" . \"($filesize != $info->{size})\\n\";\n        }\n\n        # check permission for all used storages\n        my $pool = $opts->{pool};\n        if ($user ne 'root@pam') {\n            if (defined($opts->{storage})) {\n                my $sid = $opts->{storage} || 'local';\n                $rpcenv->check($user, \"/storage/$sid\", ['Datastore.AllocateSpace']);\n            } else {\n                foreach my $fn (keys %$map) {\n                    my $fi = $map->{$fn};\n                    my $sid = $fi->{storeid} || 'local';\n                    $rpcenv->check($user, \"/storage/$sid\", ['Datastore.AllocateSpace']);\n                }\n            }\n        }\n\n        my $storeid;\n        if (defined($opts->{storage})) {\n            $storeid = $opts->{storage} || 'local';\n        } else {\n            $storeid = $info->{storeid} || 'local';\n        }\n\n        my $cfg = PVE::Storage::config();\n        my $scfg = PVE::Storage::storage_config($cfg, $storeid);\n\n        my $alloc_size = int(($filesize + 1024 - 1) / 1024);\n        if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {\n            # hack: we just alloc a small file (32K) - we overwrite it anyways\n            $alloc_size = 32;\n        } else {\n            die \"unable to restore '$filename' to storage '$storeid'\\n\"\n                . \"storage type '$scfg->{type}' does not support format '$format\\n\"\n                if $format ne 'raw';\n        }\n\n        my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid, $format, undef, $alloc_size);\n\n        print STDERR \"new volume ID is '$volid'\\n\";\n\n        print $statfd \"vzdump:$info->{virtdev}:$volid\\n\";\n        $statfd->close();\n\n        $path = PVE::Storage::path($cfg, $volid);\n    }\n\n    print STDERR \"restore data to '$path' ($filesize bytes)\\n\";\n\n    if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) {\n        exec 'dd', 'ibs=256K', 'obs=256K', \"of=$path\";\n        die \"couldn't exec dd: $!\\n\";\n    } else {\n        exec '/bin/cp', '--sparse=always', '/dev/stdin', $path;\n        die \"couldn't exec cp: $!\\n\";\n    }\n}\n\nif (scalar(@ARGV) == 2) {\n    my $archive = shift;\n    my $vmid = shift;\n\n    # fixme: use API call\n    PVE::JSONSchema::pve_verify_vmid($vmid);\n\n    PVE::Cluster::check_cfs_quorum();\n\n    PVE::QemuServer::restore_archive($archive, $vmid, 'root@pam', $opts);\n\n} elsif (scalar(@ARGV) == 0 && $ENV{TAR_FILENAME}) {\n    extract_archive();\n} else {\n    print_usage();\n    exit(-1);\n}\n\nexit(0);\n\n"
  },
  {
    "path": "src/bin/qmrestore",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse PVE::CLI::qmrestore;\n\nPVE::CLI::qmrestore->run_cli_handler();\n"
  },
  {
    "path": "src/qmeventd/.clang-format",
    "content": "BasedOnStyle: LLVM\nColumnLimit: 100\nIndentWidth: 4\nAlignAfterOpenBracket: BlockIndent\nBinPackParameters: false # TODO: evaluate using OnePerLine (needs clang 20+) for a balanance?\nAlwaysBreakBeforeMultilineStrings: true\nAllowShortIfStatementsOnASingleLine: false\nInsertBraces: true\nAllowShortEnumsOnASingleLine: false\n"
  },
  {
    "path": "src/qmeventd/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nSBINDIR=$(PREFIX)/sbin\nSERVICEDIR=$(PREFIX)/lib/systemd/system\nMANDIR=$(PREFIX)/share/man\n\n-include /usr/share/pve-doc-generator/pve-doc-generator.mk\n\nCC ?= gcc\nCFLAGS += -O2 -Werror -Wall -Wextra -Wpedantic -Wtype-limits -Wl,-z,relro -std=gnu11\nCFLAGS += $(shell pkgconf --cflags json-c glib-2.0)\nLDFLAGS += $(shell pkgconf --libs json-c glib-2.0)\n\nqmeventd: qmeventd.c\n\t$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)\n\ndocs: qmeventd.8\n\n.PHONY: install\ninstall: qmeventd docs\n\tinstall -d $(DESTDIR)/$(SBINDIR)\n\tinstall -m 0755 qmeventd $(DESTDIR)/$(SBINDIR)\n\tinstall -d $(DESTDIR)/$(SERVICEDIR)\n\tinstall -m 0644 qmeventd.service $(DESTDIR)/$(SERVICEDIR)\n\tinstall -d $(DESTDIR)/$(MANDIR)/man8\n\tinstall -m 0644 qmeventd.8 $(DESTDIR)/$(MANDIR)/man8\n\n.PHONY: clean\nclean:\n\t$(MAKE) cleanup-docgen\n\trm -rf qmeventd\n"
  },
  {
    "path": "src/qmeventd/qmeventd.c",
    "content": "// SPDX-License-Identifier: AGPL-3.0-or-later\n/*\n    Copyright (C) 2018 - 2021 Proxmox Server Solutions GmbH\n\n    Author: Dominik Csapak <d.csapak@proxmox.com>\n    Author: Stefan Reiter <s.reiter@proxmox.com>\n\n    Description:\n\n    qmeventd listens on a given socket, and waits for qemu processes to\n    connect. After accepting a connection qmeventd waits for shutdown events\n    followed by the closing of the socket. Once that happens `qm cleanup` will\n    be executed with following three arguments:\n    VMID <graceful> <guest>\n    Where `graceful` can be `1` or `0` depending if shutdown event was observed\n    before the socket got closed. The second parameter `guest` is also boolean\n    `1` or `0` depending if the shutdown was requested from the guest OS\n    (i.e., the \"inside\").\n*/\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <fcntl.h>\n#include <gmodule.h>\n#include <json.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/socket.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <sys/wait.h>\n#include <time.h>\n#include <unistd.h>\n\n#include \"qmeventd.h\"\n\n#define DEFAULT_KILL_TIMEOUT 60\n\nstatic int verbose = 0;\nstatic int kill_timeout = DEFAULT_KILL_TIMEOUT;\nstatic int epoll_fd = 0;\nstatic const char *progname;\nGHashTable *vm_clients; // key=vmid (freed on remove), value=*Client (free manually)\nGSList *forced_cleanups;\nstatic int needs_cleanup = 0;\n\n/*\n * Helper functions\n */\n\nstatic void usage() {\n    fprintf(stderr, \"Usage: %s [-f] [-v] PATH\\n\", progname);\n    fprintf(stderr, \"  -f       run in foreground (default: false)\\n\");\n    fprintf(stderr, \"  -v       verbose (default: false)\\n\");\n    fprintf(stderr, \"  -t <s>   kill timeout (default: %ds)\\n\", DEFAULT_KILL_TIMEOUT);\n    fprintf(stderr, \"  PATH     use PATH for socket\\n\");\n}\n\nstatic pid_t get_pid_from_fd(int fd) {\n    struct ucred credentials = {.pid = 0, .uid = 0, .gid = 0};\n    socklen_t len = sizeof(struct ucred);\n    log_neg(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &len), \"getsockopt\");\n    return credentials.pid;\n}\n\n/*\n * parses the vmid from the qemu.slice entry of /proc/<pid>/cgroup\n */\nstatic unsigned long get_vmid_from_pid(pid_t pid) {\n    char filename[32] = {0};\n    int len = snprintf(filename, sizeof(filename), \"/proc/%d/cmdline\", pid);\n    if (len < 0) {\n        fprintf(stderr, \"error during snprintf for %d: %s\\n\", pid, strerror(errno));\n        return 0;\n    }\n    if ((size_t)len >= sizeof(filename)) {\n        fprintf(stderr, \"error: pid %d too long\\n\", pid);\n        return 0;\n    }\n    FILE *fp = fopen(filename, \"re\");\n    if (fp == NULL) {\n        fprintf(stderr, \"error opening %s: %s\\n\", filename, strerror(errno));\n        return 0;\n    }\n\n    unsigned long vmid = 0;\n    char *buf = NULL;\n    size_t buflen = 0;\n\n    while (getdelim(&buf, &buflen, '\\0', fp) >= 0) {\n        if (strcmp(buf, \"-pidfile\")) {\n            continue;\n        }\n\n        if (getdelim(&buf, &buflen, '\\0', fp) < 0) {\n            break;\n        }\n\n        char *vmid_start = strrchr(buf, '/');\n        if (!vmid_start) {\n            fprintf(stderr, \"unexpected pidfile option %s\\n\", buf);\n            break;\n        }\n        vmid_start++;\n\n        if (vmid_start[0] == '-' || vmid_start[0] == '\\0') {\n            fprintf(stderr, \"invalid vmid in pidfile option %s\\n\", buf);\n            break;\n        }\n\n        errno = 0;\n        char *endptr = NULL;\n        vmid = strtoul(vmid_start, &endptr, 10);\n        if (!endptr || strcmp(endptr, \".pid\")) {\n            fprintf(stderr, \"unexpected pidfile option %s\\n\", buf);\n            vmid = 0;\n            break;\n        }\n        if (errno != 0) {\n            vmid = 0;\n        }\n\n        break;\n    }\n\n    if (errno) {\n        fprintf(stderr, \"error parsing vmid for %d: %s\\n\", pid, strerror(errno));\n    } else if (!vmid) {\n        fprintf(stderr, \"error parsing vmid for %d: no matching pidfile option\\n\", pid);\n    }\n\n    free(buf);\n    fclose(fp);\n    return vmid;\n}\n\nstatic bool must_write(int fd, const char *buf, size_t len) {\n    ssize_t wlen;\n    do {\n        wlen = write(fd, buf, len);\n    } while (wlen < 0 && errno == EINTR);\n\n    return (wlen == (ssize_t)len);\n}\n\n/*\n * qmp handling functions\n */\n\nstatic void send_qmp_cmd(struct Client *client, const char *buf, size_t len) {\n    if (!must_write(client->fd, buf, len - 1)) {\n        fprintf(stderr, \"%s: cannot send QMP message\\n\", client->qemu.vmid);\n        cleanup_client(client);\n    }\n}\n\nvoid handle_qmp_handshake(struct Client *client) {\n    VERBOSE_PRINT(\"pid%d: got QMP handshake, assuming QEMU client\\n\", client->pid);\n\n    // extract vmid from cmdline, now that we know it's a QEMU process\n    unsigned long vmid = get_vmid_from_pid(client->pid);\n    int res = snprintf(client->qemu.vmid, sizeof(client->qemu.vmid), \"%lu\", vmid);\n    if (vmid == 0 || res < 0 || res >= (int)sizeof(client->qemu.vmid)) {\n        fprintf(stderr, \"could not get vmid from pid %d\\n\", client->pid);\n        cleanup_client(client);\n        return;\n    }\n\n    VERBOSE_PRINT(\"pid%d: assigned VMID: %s\\n\", client->pid, client->qemu.vmid);\n    client->type = CLIENT_QEMU;\n    if (!g_hash_table_insert(vm_clients, strdup(client->qemu.vmid), client)) {\n        // not fatal, just means backup handling won't work\n        fprintf(stderr, \"%s: could not insert client into VMID->client table\\n\", client->qemu.vmid);\n    }\n\n    static const char qmp_answer[] = \"{\\\"execute\\\":\\\"qmp_capabilities\\\"}\\n\";\n    send_qmp_cmd(client, qmp_answer, sizeof(qmp_answer));\n}\n\nvoid handle_qmp_event(struct Client *client, struct json_object *obj) {\n    struct json_object *event;\n    if (!json_object_object_get_ex(obj, \"event\", &event)) {\n        return;\n    }\n    VERBOSE_PRINT(\"%s: got QMP event: %s\\n\", client->qemu.vmid, json_object_get_string(event));\n\n    if (client->state == STATE_TERMINATING) {\n        // QEMU sometimes sends a second SHUTDOWN after SIGTERM, ignore\n        VERBOSE_PRINT(\"%s: event was after termination, ignoring\\n\", client->qemu.vmid);\n        return;\n    }\n\n    // event, check if shutdown and get guest parameter\n    if (!strcmp(json_object_get_string(event), \"SHUTDOWN\")) {\n        client->qemu.graceful = 1;\n        struct json_object *data;\n        struct json_object *guest;\n        if (json_object_object_get_ex(obj, \"data\", &data) &&\n            json_object_object_get_ex(data, \"guest\", &guest)) {\n            client->qemu.guest = (unsigned short)json_object_get_boolean(guest);\n        }\n\n        // check if a backup is running and kill QEMU process if not\n        terminate_check(client);\n    }\n}\n\nvoid terminate_check(struct Client *client) {\n    if (client->state != STATE_IDLE) {\n        // if we're already in a request, queue this one until after\n        VERBOSE_PRINT(\"%s: terminate_check queued\\n\", client->qemu.vmid);\n        client->qemu.term_check_queued = true;\n        return;\n    }\n\n    client->qemu.term_check_queued = false;\n\n    VERBOSE_PRINT(\"%s: query-status\\n\", client->qemu.vmid);\n    client->state = STATE_EXPECT_STATUS_RESP;\n    static const char qmp_req[] = \"{\\\"execute\\\":\\\"query-status\\\"}\\n\";\n    send_qmp_cmd(client, qmp_req, sizeof(qmp_req));\n}\n\nvoid handle_qmp_return(struct Client *client, struct json_object *data, bool error) {\n    if (error) {\n        const char *msg = \"n/a\";\n        struct json_object *desc;\n        if (json_object_object_get_ex(data, \"desc\", &desc)) {\n            msg = json_object_get_string(desc);\n        }\n        fprintf(stderr, \"%s: received error from QMP: %s\\n\", client->qemu.vmid, msg);\n        client->state = STATE_IDLE;\n        goto out;\n    }\n\n    struct json_object *status;\n    json_bool has_status = data && json_object_object_get_ex(data, \"status\", &status);\n\n    bool active = false;\n    if (has_status) {\n        const char *status_str = json_object_get_string(status);\n        active =\n            status_str && (!strcmp(status_str, \"running\") || !strcmp(status_str, \"paused\") ||\n                           !strcmp(status_str, \"suspended\") || !strcmp(status_str, \"prelaunch\"));\n    }\n\n    switch (client->state) {\n    case STATE_EXPECT_STATUS_RESP:\n        client->state = STATE_IDLE;\n        if (active) {\n            VERBOSE_PRINT(\"%s: got status: VM is active\\n\", client->qemu.vmid);\n        } else if (!client->qemu.backup) {\n            terminate_client(client);\n        } else {\n            // if we're in a backup, don't do anything, vzdump will notify\n            // us when the backup finishes\n            VERBOSE_PRINT(\"%s: not active, but running backup - keep alive\\n\", client->qemu.vmid);\n        }\n        break;\n\n    // this means we received the empty return from our handshake answer\n    case STATE_HANDSHAKE:\n        client->state = STATE_IDLE;\n        VERBOSE_PRINT(\"%s: QMP handshake complete\\n\", client->qemu.vmid);\n        break;\n\n    // we expect an empty return object after sending quit\n    case STATE_TERMINATING:\n        break;\n    case STATE_IDLE:\n        VERBOSE_PRINT(\"%s: spurious return value received\\n\", client->qemu.vmid);\n        break;\n    }\n\nout:\n    if (client->qemu.term_check_queued) {\n        terminate_check(client);\n    }\n}\n\n/*\n * VZDump specific client functions\n */\n\nvoid handle_vzdump_handshake(struct Client *client, struct json_object *data) {\n    client->state = STATE_IDLE;\n\n    struct json_object *vmid_obj;\n    json_bool has_vmid = data && json_object_object_get_ex(data, \"vmid\", &vmid_obj);\n\n    if (!has_vmid) {\n        VERBOSE_PRINT(\"pid%d: invalid vzdump handshake: no vmid\\n\", client->pid);\n        return;\n    }\n\n    const char *vmid_str = json_object_get_string(vmid_obj);\n\n    if (!vmid_str) {\n        VERBOSE_PRINT(\"pid%d: invalid vzdump handshake: vmid is not a string\\n\", client->pid);\n        return;\n    }\n\n    int res = snprintf(client->vzdump.vmid, sizeof(client->vzdump.vmid), \"%s\", vmid_str);\n    if (res < 0 || res >= (int)sizeof(client->vzdump.vmid)) {\n        VERBOSE_PRINT(\"pid%d: invalid vzdump handshake: vmid too long or invalid\\n\", client->pid);\n        return;\n    }\n\n    struct Client *vmc = (struct Client *)g_hash_table_lookup(vm_clients, client->vzdump.vmid);\n    if (vmc) {\n        vmc->qemu.backup = true;\n\n        // only mark as VZDUMP once we have set everything up, otherwise 'cleanup'\n        // might try to access an invalid value\n        client->type = CLIENT_VZDUMP;\n        VERBOSE_PRINT(\"%s: vzdump backup started\\n\", client->vzdump.vmid);\n    } else {\n        VERBOSE_PRINT(\n            \"%s: vzdump requested backup start for unregistered VM\\n\", client->vzdump.vmid\n        );\n    }\n}\n\n/*\n * client management functions\n */\n\nvoid add_new_client(int client_fd) {\n    struct Client *client = calloc(1, sizeof(struct Client));\n    if (client == NULL) {\n        fprintf(stderr, \"could not add new client - allocation failed!\\n\");\n        fflush(stderr);\n        return;\n    }\n    client->state = STATE_HANDSHAKE;\n    client->type = CLIENT_NONE;\n    client->fd = client_fd;\n    client->pid = get_pid_from_fd(client_fd);\n    if (client->pid == 0) {\n        fprintf(stderr, \"could not get pid from client\\n\");\n        goto err;\n    }\n\n    struct epoll_event ev;\n    ev.events = EPOLLIN;\n    ev.data.ptr = client;\n    int res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);\n    if (res < 0) {\n        perror(\"epoll_ctl client add\");\n        goto err;\n    }\n\n    VERBOSE_PRINT(\"added new client, pid: %d\\n\", client->pid);\n\n    return;\nerr:\n    (void)close(client_fd);\n    free(client);\n}\n\nstatic void cleanup_qemu_client(struct Client *client) {\n    unsigned short graceful = client->qemu.graceful;\n    unsigned short guest = client->qemu.guest;\n    char vmid[sizeof(client->qemu.vmid)];\n    strncpy(vmid, client->qemu.vmid, sizeof(vmid));\n    g_hash_table_remove(vm_clients, &vmid); // frees key, ignore errors\n    VERBOSE_PRINT(\"%s: executing cleanup (graceful: %d, guest: %d)\\n\", vmid, graceful, guest);\n\n    int pid = fork();\n    if (pid < 0) {\n        fprintf(stderr, \"fork failed: %s\\n\", strerror(errno));\n        return;\n    }\n    if (pid == 0) {\n        char *script = \"/usr/sbin/qm\";\n\n        char *args[] = {script, \"cleanup\", vmid, graceful ? \"1\" : \"0\", guest ? \"1\" : \"0\", NULL};\n\n        execvp(script, args);\n        perror(\"execvp\");\n        _exit(1);\n    }\n}\n\nvoid cleanup_client(struct Client *client) {\n    log_neg(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client->fd, NULL), \"epoll del\");\n    (void)close(client->fd);\n\n    struct Client *vmc;\n    switch (client->type) {\n    case CLIENT_QEMU:\n        cleanup_qemu_client(client);\n        break;\n\n    case CLIENT_VZDUMP:\n        vmc = (struct Client *)g_hash_table_lookup(vm_clients, client->vzdump.vmid);\n        if (vmc) {\n            VERBOSE_PRINT(\"%s: backup ended\\n\", client->vzdump.vmid);\n            vmc->qemu.backup = false;\n            terminate_check(vmc);\n        }\n        break;\n\n    case CLIENT_NONE:\n        // do nothing, only close socket\n        break;\n    }\n\n    if (client->pidfd > 0) {\n        (void)close(client->pidfd);\n    }\n    VERBOSE_PRINT(\"removing %s from forced cleanups\\n\", client->qemu.vmid);\n    forced_cleanups = g_slist_remove(forced_cleanups, client);\n    free(client);\n}\n\nvoid terminate_client(struct Client *client) {\n    VERBOSE_PRINT(\"%s: terminating client (pid %d)\\n\", client->qemu.vmid, client->pid);\n\n    client->state = STATE_TERMINATING;\n\n    // open a pidfd before kill for later cleanup\n    int pidfd = pidfd_open(client->pid, 0);\n    if (pidfd < 0) {\n        switch (errno) {\n        case ESRCH:\n            // process already dead for some reason, cleanup done\n            VERBOSE_PRINT(\n                \"%s: failed to open pidfd, process already dead (pid %d)\\n\", client->qemu.vmid,\n                client->pid\n            );\n            return;\n\n        // otherwise fall back to just using the PID directly, but don't\n        // print if we only failed because we're running on an older kernel\n        case ENOSYS:\n            break;\n        default:\n            perror(\"failed to open QEMU pidfd for cleanup\");\n            break;\n        }\n    }\n\n    // try to send a 'quit' command first, fallback to SIGTERM of the pid\n    static const char qmp_quit_command[] = \"{\\\"execute\\\":\\\"quit\\\"}\\n\";\n    VERBOSE_PRINT(\"%s: sending 'quit' via QMP\\n\", client->qemu.vmid);\n    if (!must_write(client->fd, qmp_quit_command, sizeof(qmp_quit_command) - 1)) {\n        VERBOSE_PRINT(\"%s: sending 'SIGTERM' to pid %d\\n\", client->qemu.vmid, client->pid);\n        int err = kill(client->pid, SIGTERM);\n        log_neg(err, \"kill\");\n    }\n\n    time_t timeout = time(NULL) + kill_timeout;\n\n    client->pidfd = pidfd;\n    client->timeout = timeout;\n\n    forced_cleanups = g_slist_prepend(forced_cleanups, (void *)client);\n    needs_cleanup = 1;\n}\n\nvoid handle_client(struct Client *client) {\n    VERBOSE_PRINT(\"pid%d: entering handle\\n\", client->pid);\n    ssize_t len;\n    do {\n        len =\n            read(client->fd, (client->buf + client->buflen), sizeof(client->buf) - client->buflen);\n    } while (len < 0 && errno == EINTR);\n\n    if (len < 0) {\n        if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {\n            log_neg((int)len, \"read\");\n            cleanup_client(client);\n        }\n        return;\n    } else if (len == 0) {\n        VERBOSE_PRINT(\"pid%d: got EOF\\n\", client->pid);\n        cleanup_client(client);\n        return;\n    }\n\n    VERBOSE_PRINT(\"pid%d: read %ld bytes\\n\", client->pid, len);\n    client->buflen += len;\n\n    struct json_tokener *tok = json_tokener_new();\n    struct json_object *jobj = NULL;\n    enum json_tokener_error jerr = json_tokener_success;\n    while (jerr == json_tokener_success && client->buflen != 0) {\n        jobj = json_tokener_parse_ex(tok, client->buf, (int)client->buflen);\n        jerr = json_tokener_get_error(tok);\n        unsigned int offset = (unsigned int)tok->char_offset;\n        switch (jerr) {\n        case json_tokener_success:\n            // move rest from buffer to front\n            memmove(client->buf, client->buf + offset, client->buflen - offset);\n            client->buflen -= offset;\n            if (json_object_is_type(jobj, json_type_object)) {\n                struct json_object *obj;\n                if (json_object_object_get_ex(jobj, \"QMP\", &obj)) {\n                    handle_qmp_handshake(client);\n                } else if (json_object_object_get_ex(jobj, \"event\", &obj)) {\n                    handle_qmp_event(client, jobj);\n                } else if (json_object_object_get_ex(jobj, \"return\", &obj)) {\n                    handle_qmp_return(client, obj, false);\n                } else if (json_object_object_get_ex(jobj, \"error\", &obj)) {\n                    handle_qmp_return(client, obj, true);\n                } else if (json_object_object_get_ex(jobj, \"vzdump\", &obj)) {\n                    handle_vzdump_handshake(client, obj);\n                } // else ignore message\n            }\n            break;\n        case json_tokener_continue:\n            if (client->buflen >= sizeof(client->buf)) {\n                VERBOSE_PRINT(\"pid%d: msg too large, discarding buffer\\n\", client->pid);\n                memset(client->buf, 0, sizeof(client->buf));\n                client->buflen = 0;\n            } // else we have enough space try again after next read\n            break;\n        default:\n            VERBOSE_PRINT(\"pid%d: parse error: %d, discarding buffer\\n\", client->pid, jerr);\n            memset(client->buf, 0, client->buflen);\n            client->buflen = 0;\n            break;\n        }\n        json_object_put(jobj);\n    }\n    json_tokener_free(tok);\n}\n\nstatic void sigkill(void *ptr, void *time_ptr) {\n    struct Client *data = ptr;\n    int err;\n\n    if (data->timeout != 0 && data->timeout > *(time_t *)time_ptr) {\n        return;\n    }\n\n    if (data->pidfd > 0) {\n        err = pidfd_send_signal(data->pidfd, SIGKILL, NULL, 0);\n        (void)close(data->pidfd);\n        data->pidfd = -1;\n    } else {\n        err = kill(data->pid, SIGKILL);\n    }\n\n    if (err < 0) {\n        if (errno != ESRCH) {\n            fprintf(\n                stderr, \"SIGKILL cleanup of pid '%d' failed - %s\\n\", data->pid, strerror(errno)\n            );\n        }\n    } else {\n        fprintf(stderr, \"cleanup failed, terminating pid '%d' with SIGKILL\\n\", data->pid);\n    }\n\n    data->timeout = 0;\n\n    // remove ourselves from the list\n    forced_cleanups = g_slist_remove(forced_cleanups, ptr);\n}\n\nstatic void handle_forced_cleanup() {\n    if (g_slist_length(forced_cleanups) > 0) {\n        VERBOSE_PRINT(\"clearing forced cleanup backlog\\n\");\n        time_t cur_time = time(NULL);\n        g_slist_foreach(forced_cleanups, sigkill, &cur_time);\n    }\n    needs_cleanup = g_slist_length(forced_cleanups) > 0;\n}\n\nint main(int argc, char *argv[]) {\n    int opt;\n    int daemonize = 1;\n    char *socket_path = NULL;\n    progname = argv[0];\n\n    while ((opt = getopt(argc, argv, \"hfvt:\")) != -1) {\n        switch (opt) {\n        case 'f':\n            daemonize = 0;\n            break;\n        case 'v':\n            verbose = 1;\n            break;\n        case 't':\n            errno = 0;\n            char *endptr = NULL;\n            kill_timeout = strtoul(optarg, &endptr, 10);\n            if (errno != 0 || *endptr != '\\0' || kill_timeout == 0) {\n                usage();\n                exit(EXIT_FAILURE);\n            }\n            break;\n        case 'h':\n            usage();\n            exit(EXIT_SUCCESS);\n            break;\n        default:\n            usage();\n            exit(EXIT_FAILURE);\n        }\n    }\n\n    if (optind >= argc) {\n        usage();\n        exit(EXIT_FAILURE);\n    }\n\n    signal(SIGCHLD, SIG_IGN);\n\n    socket_path = argv[optind];\n\n    int sock = socket(AF_UNIX, SOCK_STREAM, 0);\n    bail_neg(sock, \"socket\");\n\n    struct sockaddr_un addr;\n    memset(&addr, 0, sizeof(addr));\n    addr.sun_family = AF_UNIX;\n    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);\n\n    unlink(socket_path);\n    bail_neg(bind(sock, (struct sockaddr *)&addr, sizeof(addr)), \"bind\");\n\n    struct epoll_event ev, events[1];\n    epoll_fd = epoll_create1(EPOLL_CLOEXEC);\n    bail_neg(epoll_fd, \"epoll_create1\");\n\n    ev.events = EPOLLIN;\n    ev.data.fd = sock;\n    bail_neg(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev), \"epoll_ctl\");\n\n    bail_neg(listen(sock, 10), \"listen\");\n\n    if (daemonize) {\n        bail_neg(daemon(0, 1), \"daemon\");\n    }\n\n    vm_clients = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL);\n\n    int nevents;\n\n    for (;;) {\n        nevents = epoll_wait(epoll_fd, events, 1, needs_cleanup ? 10 * 1000 : -1);\n        if (nevents < 0 && errno == EINTR) {\n            continue;\n        }\n        bail_neg(nevents, \"epoll_wait\");\n\n        for (int n = 0; n < nevents; n++) {\n            if (events[n].data.fd == sock) {\n\n                int conn_sock = accept4(sock, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);\n                log_neg(conn_sock, \"accept\");\n                if (conn_sock > -1) {\n                    add_new_client(conn_sock);\n                }\n            } else {\n                handle_client((struct Client *)events[n].data.ptr);\n            }\n        }\n        handle_forced_cleanup();\n    }\n}\n"
  },
  {
    "path": "src/qmeventd/qmeventd.h",
    "content": "// SPDX-License-Identifier: AGPL-3.0-or-later\n/*\n    Copyright (C) 2018 - 2021 Proxmox Server Solutions GmbH\n\n    Author: Dominik Csapak <d.csapak@proxmox.com>\n    Author: Stefan Reiter <s.reiter@proxmox.com>\n*/\n\n#include <sys/syscall.h>\n#include <time.h>\n\n#ifndef __NR_pidfd_open\n#define __NR_pidfd_open 434\n#endif\n#ifndef __NR_pidfd_send_signal\n#define __NR_pidfd_send_signal 424\n#endif\n\n#define VERBOSE_PRINT(...)                                                                         \\\n    do {                                                                                           \\\n        if (verbose) {                                                                             \\\n            printf(__VA_ARGS__);                                                                   \\\n            fflush(stdout);                                                                        \\\n        }                                                                                          \\\n    } while (0)\n\nstatic inline void log_neg(int errval, const char *msg) {\n    if (errval < 0) {\n        perror(msg);\n    }\n}\n\nstatic inline void bail_neg(int errval, const char *msg) {\n    if (errval < 0) {\n        perror(msg);\n        exit(EXIT_FAILURE);\n    }\n}\n\nstatic inline int pidfd_open(pid_t pid, unsigned int flags) {\n    return syscall(__NR_pidfd_open, pid, flags);\n}\n\nstatic inline int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags) {\n    return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);\n}\n\ntypedef enum {\n    CLIENT_NONE,\n    CLIENT_QEMU,\n    CLIENT_VZDUMP\n} ClientType;\n\ntypedef enum {\n    STATE_HANDSHAKE,\n    STATE_IDLE,\n    STATE_EXPECT_STATUS_RESP,\n    STATE_TERMINATING\n} ClientState;\n\nstruct Client {\n    char buf[4096];\n    unsigned int buflen;\n\n    int fd;\n    pid_t pid;\n    int pidfd;\n    time_t timeout;\n\n    ClientType type;\n    ClientState state;\n\n    // only relevant for type=CLIENT_QEMU\n    struct {\n        char vmid[16];\n        unsigned short graceful;\n        unsigned short guest;\n        bool term_check_queued;\n        bool backup;\n    } qemu;\n\n    // only relevant for type=CLIENT_VZDUMP\n    struct {\n        // vmid of referenced backup\n        char vmid[16];\n    } vzdump;\n};\n\nvoid handle_qmp_handshake(struct Client *client);\nvoid handle_qmp_event(struct Client *client, struct json_object *obj);\nvoid handle_qmp_return(struct Client *client, struct json_object *data, bool error);\nvoid handle_vzdump_handshake(struct Client *client, struct json_object *data);\nvoid handle_client(struct Client *client);\nvoid add_new_client(int client_fd);\nvoid cleanup_client(struct Client *client);\nvoid terminate_client(struct Client *client);\nvoid terminate_check(struct Client *client);\n"
  },
  {
    "path": "src/qmeventd/qmeventd.service",
    "content": "[Unit]\nDescription=PVE Qemu Event Daemon\nRequiresMountsFor=/var/run\nBefore=pve-ha-lrm.service\nBefore=pve-guests.service\n\n[Service]\nExecStart=/usr/sbin/qmeventd /var/run/qmeventd.sock\nType=forking\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "src/query-machine-capabilities/Makefile",
    "content": "DESTDIR=\nPREFIX=/usr\nBINDIR=$(PREFIX)/libexec/qemu-server\nSERVICEDIR=$(PREFIX)/lib/systemd/system\n\nCC ?= gcc\nCFLAGS += -O2 -fanalyzer -Werror -Wall -Wextra -Wpedantic -Wtype-limits -Wl,-z,relro -std=gnu11\n\nquery-machine-capabilities: query-machine-capabilities.c\n\t$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)\n\n.PHONY: install\ninstall: query-machine-capabilities\n\tinstall -d $(DESTDIR)/$(BINDIR)\n\tinstall -m 0755 query-machine-capabilities $(DESTDIR)$(BINDIR)\n\tinstall -d $(DESTDIR)/$(SERVICEDIR)\n\tinstall -m 0644 pve-query-machine-capabilities.service $(DESTDIR)$(SERVICEDIR)\n\n.PHONY: clean\nclean:\n\trm -f query-machine-capabilities\n"
  },
  {
    "path": "src/query-machine-capabilities/pve-query-machine-capabilities.service",
    "content": "[Unit]\nDescription=PVE Query Machine Capabilities\nRequiresMountsFor=/run\nBefore=pve-ha-lrm.service\nBefore=pve-guests.service\n\n[Service]\nExecStart=/usr/libexec/qemu-server/query-machine-capabilities\nType=oneshot\nRemainAfterExit=yes\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "src/query-machine-capabilities/query-machine-capabilities.c",
    "content": "#include <stdio.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <sys/stat.h>\n#include <errno.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#ifdef __aarch64__\n#include <sys/auxv.h>\n#ifndef HWCAP_AES\n#define HWCAP_AES (1 << 3)\n#endif\n#ifndef HWCAP_SHA2\n#define HWCAP_SHA2 (1 << 6)\n#endif\n#endif // __aarch64__\n\n#define eprintf(...) fprintf(stderr, __VA_ARGS__)\n\n#define OUTPUT_DIR \"/run/qemu-server\"\n#define OUTPUT_FILENAME \"host-hw-capabilities.json\"\n#define OUTPUT_PATH OUTPUT_DIR \"/\" OUTPUT_FILENAME\n\ntypedef struct {\n    bool sev_support;\n    bool sev_es_support;\n    bool sev_snp_support;\n\n    uint8_t cbitpos;\n    uint8_t reduced_phys_bits;\n} cpu_caps_amd_sev_t;\n\ntypedef struct {\n    bool tdx_support;\n} cpu_caps_intel_tdx_t;\n\ntypedef struct {\n    bool aes;\n    bool sha2;\n} cpu_caps_arm_t;\n\nstatic inline void cpu_vendor(char vendor[13]) {\n#ifdef __x86_64__\n    uint32_t eax;\n    uint32_t *vp = (uint32_t *)vendor;\n    asm volatile(\"cpuid\"\n        : \"=a\"(eax), \"=b\"(*vp), \"=c\"(*(vp+2)), \"=d\"(*(vp+1))\n        : \"a\"(0)\n    );\n#elif defined(__aarch64__)\n    // just parse /proc/cpuinfo as the MIDR_EL1 mrs might not be available to read from user space\n    FILE *f = fopen(\"/proc/cpuinfo\", \"r\");\n    int implementer = 0;\n    if (f) {\n        char line[256];\n        while (fgets(line, sizeof(line), f)) {\n            if (strncmp(line, \"CPU implementer\", 15) == 0) {\n                char *p = strchr(line, ':');\n                if (p) implementer = (int)strtol(p + 1, NULL, 0);\n                break;\n            }\n        }\n        fclose(f);\n    }\n\n    // mapping taken from arch/arm64/include/asm/cputype.h (e.g. ARM_CPU_IMP_ARM)\n    switch(implementer) {\n        case 0x41: strcpy(vendor, \"ARM Limited\"); break;\n        case 0x42: strcpy(vendor, \"Broadcom\"); break;\n        case 0x43: strcpy(vendor, \"Cavium\"); break;\n        case 0x48: strcpy(vendor, \"HiSilicon\"); break;\n        case 0x4E: strcpy(vendor, \"NVIDIA\"); break;\n        case 0x51: strcpy(vendor, \"Qualcomm\"); break;\n        case 0x53: strcpy(vendor, \"Samsung\"); break;\n        case 0x61: strcpy(vendor, \"Apple\"); break;\n        case 0xC0: strcpy(vendor, \"Ampere\"); break;\n        default: snprintf(vendor, 13, \"ARM64:%02x\", implementer); break;\n    }\n#else\n    strcpy(vendor, \"Unknown\");\n#endif\n    vendor[12] = '\\0';\n}\n\nint read_msr(uint32_t msr_index, uint64_t *value) {\n    uint64_t data;\n    char* msr_file_name = \"/dev/cpu/0/msr\";\n    int fd;\n\n    fd = open(msr_file_name, O_RDONLY);\n    if (fd < 0) {\n        if (errno == ENXIO) {\n            eprintf(\"rdmsr: No CPU 0\\n\");\n            return -1;\n        } else if (errno == EIO) {\n            eprintf(\"rdmsr: CPU doesn't support MSRs\\n\");\n            return -1;\n        } else {\n            perror(\"rdmsr: failed to open MSR\");\n            return -1;\n        }\n    }\n\n    if (pread(fd, &data, sizeof(data), msr_index) != sizeof(data)) {\n        if (errno == EIO) {\n            eprintf(\"rdmsr: CPU cannot read MSR 0x%08x\\n\", msr_index);\n            return -1;\n        } else {\n            perror(\"rdmsr: pread\");\n            return -1;\n        }\n    }\n\n    *value = data;\n\n    close(fd);\n    return 0;\n}\n\nvoid query_cpu_capabilities_sev(cpu_caps_amd_sev_t *res) {\n#ifdef __x86_64__\n    uint32_t eax, ebx, ecx, edx;\n\n    // query Encrypted Memory Capabilities, see:\n    // https://en.wikipedia.org/wiki/CPUID#EAX=8000001Fh:_Encrypted_Memory_Capabilities\n    uint32_t query_function = 0x8000001F;\n    asm volatile(\"cpuid\"\n        : \"=a\"(eax), \"=b\"(ebx), \"=c\"(ecx), \"=d\"(edx)\n        : \"0\"(query_function)\n    );\n\n    res->sev_support = (eax & (1<<1)) != 0;\n    res->sev_es_support = (eax & (1<<3)) != 0;\n    res->sev_snp_support = (eax & (1<<4)) != 0;\n\n    res->cbitpos = ebx & 0x3f;\n    res->reduced_phys_bits = (ebx >> 6) & 0x3f;\n#else\n    memset(res, 0, sizeof(*res));\n#endif\n}\n\nint query_cpu_capabilities_tdx(cpu_caps_intel_tdx_t *res) {\n    uint64_t tme_value, sgx_value, tdx_value;\n\n    if (read_msr(0x982, &tme_value) == 0 && read_msr(0xa0, &sgx_value) == 0 &&\n        read_msr(0x1401, &tdx_value) == 0) {\n        res->tdx_support = ((tme_value >> 1) & 1ULL) & (!sgx_value) & ((tdx_value >> 11) & 1ULL);\n    } else {\n        eprintf(\"Intel TDX support undetermined\\n\");\n        return -1;\n    }\n    return 0;\n}\n\n#ifdef __aarch64__\nvoid query_cpu_capabilities_arm(cpu_caps_arm_t *res) {\n    unsigned long hwcaps = getauxval(AT_HWCAP);\n    res->aes = (hwcaps & HWCAP_AES);\n    res->sha2 = (hwcaps & HWCAP_SHA2);\n}\n#endif\n\nint prepare_output_directory() {\n    // Check that the directory exists and create it if it does not.\n    struct stat statbuf;\n    int ret = stat(OUTPUT_DIR, &statbuf);\n    if (ret == 0) {\n        if (!S_ISDIR(statbuf.st_mode)) {\n            eprintf(\"Path '\" OUTPUT_DIR \"' already exists but is not a directory.\\n\");\n            return 0;\n        }\n    } else if (errno == ENOENT) {\n        if (mkdir(OUTPUT_DIR, 0755) != 0) {\n            eprintf(\"Error creating directory '\" OUTPUT_DIR \"': %s\\n\", strerror(errno));\n            return 0;\n        }\n    } else {\n        eprintf(\"Error checking path '\" OUTPUT_DIR \"': %s\\n\", strerror(errno));\n        return 0;\n    }\n    return 1;\n}\n\nint main() {\n    if (!prepare_output_directory()) {\n        return 1;\n    }\n\n    FILE *file = fopen(OUTPUT_PATH, \"w\");\n    if (file == NULL) {\n        eprintf(\"Error opening to file '\" OUTPUT_PATH \"': %s\\n\", strerror(errno));\n        return 1;\n    }\n\n    char vendor[13];\n    cpu_vendor(vendor);\n\n    int ret = fprintf(file, \"{\");\n    if (ret < 0) {\n        eprintf(\"Error writing to file '\" OUTPUT_PATH \"': %s\\n\", strerror(errno));\n    }\n\n    if (strncmp(vendor, \"AuthenticAMD\", 12) == 0) {\n        cpu_caps_amd_sev_t caps_sev;\n        query_cpu_capabilities_sev(&caps_sev);\n\n        ret = fprintf(file,\n            \" \\\"amd-sev\\\": {\"\n            \" \\\"cbitpos\\\": %u,\"\n            \" \\\"reduced-phys-bits\\\": %u,\"\n            \" \\\"sev-support\\\": %s,\"\n            \" \\\"sev-support-es\\\": %s,\"\n            \" \\\"sev-support-snp\\\": %s\"\n            \" }\",\n            caps_sev.cbitpos,\n            caps_sev.reduced_phys_bits,\n            caps_sev.sev_support ? \"true\" : \"false\",\n            caps_sev.sev_es_support ? \"true\" : \"false\",\n            caps_sev.sev_snp_support ? \"true\" : \"false\"\n        );\n    } else if (strncmp(vendor, \"GenuineIntel\", 12) == 0) {\n        cpu_caps_intel_tdx_t caps_tdx;\n        if (query_cpu_capabilities_tdx(&caps_tdx) == 0) {\n            ret = fprintf(file,\n                \" \\\"intel-tdx\\\": {\"\n                \" \\\"tdx-support\\\": %s\"\n                \" }\",\n                caps_tdx.tdx_support ? \"true\" : \"false\"\n            );\n        }\n    }\n#ifdef __aarch64__\n    else {\n        cpu_caps_arm_t caps_arm;\n        query_cpu_capabilities_arm(&caps_arm);\n\n        ret = fprintf(file,\n            \" \\\"arm-caps\\\": {\"\n            \" \\\"vendor\\\": \\\"%s\\\",\"\n            \" \\\"aes\\\": %s,\"\n            \" \\\"sha2\\\": %s\"\n            \" }\",\n            vendor,\n            caps_arm.aes ? \"true\" : \"false\",\n            caps_arm.sha2 ? \"true\" : \"false\"\n        );\n    }\n#endif\n\n    if (ret < 0) {\n        eprintf(\"Error writing to file '\" OUTPUT_PATH \"': %s\\n\", strerror(errno));\n    }\n\n    ret = fprintf(file, \" }\\n\");\n    if (ret < 0) {\n        eprintf(\"Error writing to file '\" OUTPUT_PATH \"': %s\\n\", strerror(errno));\n    }\n\n    ret = fclose(file);\n    if (ret != 0) {\n        eprintf(\"Error closing file '\" OUTPUT_PATH \"': %s\\n\", strerror(errno));\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/test/Makefile",
    "content": "all: test\n\ntest: test_snapshot test_cfg_to_cmd test_cfg_to_cmd_aarch64 test_pci_addr_conflicts test_pci_reservation test_qemu_img_convert test_migration test_restore_config test_parse_config\n\ntest_snapshot: run_snapshot_tests.pl\n\t./run_snapshot_tests.pl\n\t./test_get_replicatable_volumes.pl\n\ntest_cfg_to_cmd: run_config2command_tests.pl cfg2cmd/*.conf\n\tperl -I../ ./run_config2command_tests.pl\n\ntest_cfg_to_cmd_aarch64: run_config2command_tests.pl cfg2cmd/aarch64/*.conf\n\tperl -I../ ./run_config2command_tests.pl cfg2cmd/aarch64\n\ntest_qemu_img_convert: run_qemu_img_convert_tests.pl\n\tperl -I../ ./run_qemu_img_convert_tests.pl\n\ntest_pci_addr_conflicts: run_pci_addr_checks.pl\n\t./run_pci_addr_checks.pl\n\ntest_pci_reservation: run_pci_reservation_tests.pl\n\t./run_pci_reservation_tests.pl\n\nMIGRATION_TEST_TARGETS := $(addprefix test_migration_,$(shell perl -ne 'print \"$$1 \" if /^\\s*name\\s*=>\\s*[\"'\\'']([^\\s\"'\\'']+)[\"'\\'']\\s*,\\s*$$/; END { print \"\\n\" }' run_qemu_migrate_tests.pl))\n\ntest_migration: run_qemu_migrate_tests.pl MigrationTest/*.pm $(MIGRATION_TEST_TARGETS)\n\n$(MIGRATION_TEST_TARGETS):\n\t./run_qemu_migrate_tests.pl $(@:test_migration_%=%)\n\ntest_restore_config: run_qemu_restore_config_tests.pl\n\t./run_qemu_restore_config_tests.pl\n\ntest_parse_config: run_parse_config_tests.pl\n\t./run_parse_config_tests.pl\n\n.PHONY: clean\nclean:\n\trm -rf MigrationTest/run parse-config-output\n"
  },
  {
    "path": "src/test/MigrationTest/QemuMigrateMock.pm",
    "content": "package MigrationTest::QemuMigrateMock;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse Test::MockModule;\n\nuse MigrationTest::Shared;\n\nuse PVE::API2::Qemu;\nuse PVE::QemuServer::Drive;\nuse PVE::Storage;\nuse PVE::Tools qw(file_set_contents file_get_contents);\n\nuse PVE::CLIHandler;\nuse base qw(PVE::CLIHandler);\n\nmy $RUN_DIR_PATH = $ENV{RUN_DIR_PATH} or die \"no RUN_DIR_PATH set\\n\";\nmy $QM_LIB_PATH = $ENV{QM_LIB_PATH} or die \"no QM_LIB_PATH set\\n\";\n\nmy $source_volids = decode_json(file_get_contents(\"${RUN_DIR_PATH}/source_volids\"));\nmy $source_vdisks = decode_json(file_get_contents(\"${RUN_DIR_PATH}/source_vdisks\"));\nmy $vm_status = decode_json(file_get_contents(\"${RUN_DIR_PATH}/vm_status\"));\nmy $expected_calls = decode_json(file_get_contents(\"${RUN_DIR_PATH}/expected_calls\"));\nmy $fail_config = decode_json(file_get_contents(\"${RUN_DIR_PATH}/fail_config\"));\nmy $storage_migrate_map = decode_json(file_get_contents(\"${RUN_DIR_PATH}/storage_migrate_map\"));\nmy $migrate_params = decode_json(file_get_contents(\"${RUN_DIR_PATH}/migrate_params\"));\n\nmy $test_vmid = $migrate_params->{vmid};\nmy $test_target = $migrate_params->{target};\nmy $test_opts = $migrate_params->{opts};\nmy $current_log = '';\n\nmy $vm_stop_executed = 0;\n\n# mocked modules\n\nmy $inotify_module = Test::MockModule->new(\"PVE::INotify\");\n$inotify_module->mock(\n    nodename => sub {\n        return 'pve0';\n    },\n);\n\n$MigrationTest::Shared::qemu_config_module->mock(\n    move_config_to_node => sub {\n        my ($self, $vmid, $target) = @_;\n        die \"moving wrong config: '$vmid'\\n\" if $vmid ne $test_vmid;\n        die \"moving config to wrong node: '$target'\\n\" if $target ne $test_target;\n        delete $expected_calls->{move_config_to_node};\n    },\n);\n\nmy $tunnel_module = Test::MockModule->new(\"PVE::Tunnel\");\n$tunnel_module->mock(\n    finish_tunnel => sub {\n        delete $expected_calls->{'finish_tunnel'};\n        return;\n    },\n    write_tunnel => sub {\n        my ($tunnel, $timeout, $command) = @_;\n\n        if ($command =~ m/^resume (\\d+)$/) {\n            my $vmid = $1;\n            die \"resuming wrong VM '$vmid'\\n\" if $vmid ne $test_vmid;\n            return;\n        }\n        die \"write_tunnel (mocked) - implement me: $command\\n\";\n    },\n);\n\nmy $qemu_migrate_module = Test::MockModule->new(\"PVE::QemuMigrate\");\n$qemu_migrate_module->mock(\n    fork_tunnel => sub {\n        die \"fork_tunnel (mocked) - implement me\\n\"; # currently no call should lead here\n    },\n    read_tunnel => sub {\n        die \"read_tunnel (mocked) - implement me\\n\"; # currently no call should lead here\n    },\n    start_remote_tunnel => sub {\n        my ($self, $raddr, $rport, $ruri, $unix_socket_info) = @_;\n        $expected_calls->{'finish_tunnel'} = 1;\n        $self->{tunnel} = {\n            writer => \"mocked\",\n            reader => \"mocked\",\n            pid => 123456,\n            version => 1,\n        };\n    },\n    log => sub {\n        my ($self, $level, $message) = @_;\n        $current_log .= \"$level: $message\\n\";\n    },\n    mon_cmd => sub {\n        my ($vmid, $command, %params) = @_;\n\n        if ($command eq 'nbd-server-start') {\n            return;\n        } elsif ($command eq 'block-dirty-bitmap-add') {\n            my $drive = $params{node};\n            delete $expected_calls->{\"block-dirty-bitmap-add-${drive}\"};\n            return;\n        } elsif ($command eq 'block-dirty-bitmap-remove') {\n            return;\n        } elsif ($command eq 'query-migrate') {\n            return { status => 'failed' } if $fail_config->{'query-migrate'};\n            return { status => 'completed' };\n        } elsif ($command eq 'migrate') {\n            return;\n        } elsif ($command eq 'migrate-set-parameters') {\n            return;\n        } elsif ($command eq 'migrate_cancel') {\n            return;\n        }\n        die \"mon_cmd (mocked) - implement me: $command\";\n    },\n    transfer_replication_state => sub {\n        delete $expected_calls->{transfer_replication_state};\n    },\n    switch_replication_job_target => sub {\n        delete $expected_calls->{switch_replication_job_target};\n    },\n);\n\n$MigrationTest::Shared::qemu_server_module->mock(\n    kvm_user_version => sub {\n        return \"5.0.0\";\n    },\n    vm_stop => sub {\n        $vm_stop_executed = 1;\n        delete $expected_calls->{'vm_stop'};\n    },\n);\n\nmy sub common_mirror_mock {\n    my ($vmid, $drive_id) = @_;\n\n    die \"drive_mirror with wrong vmid: '$vmid'\\n\" if $vmid ne $test_vmid;\n    die \"qemu_drive_mirror '$drive_id' error\\n\"\n        if $fail_config->{qemu_drive_mirror} && $fail_config->{qemu_drive_mirror} eq $drive_id;\n\n    my $nbd_info = decode_json(file_get_contents(\"${RUN_DIR_PATH}/nbd_info\"));\n    die \"target does not expect drive mirror for '$drive_id'\\n\"\n        if !defined($nbd_info->{$drive_id});\n    delete $nbd_info->{$drive_id};\n    file_set_contents(\"${RUN_DIR_PATH}/nbd_info\", to_json($nbd_info));\n}\n\nmy $qemu_server_blockjob_module = Test::MockModule->new(\"PVE::QemuServer::BlockJob\");\n$qemu_server_blockjob_module->mock(\n    qemu_blockjobs_cancel => sub {\n        return;\n    },\n    qemu_drive_mirror => sub {\n        my (\n            $vmid,\n            $drive_id,\n            $dst_volid,\n            $vmiddst,\n            $is_zero_initialized,\n            $jobs,\n            $completion,\n            $qga,\n            $bwlimit,\n            $src_bitmap,\n        ) = @_;\n\n        common_mirror_mock($vmid, $drive_id);\n    },\n    blockdev_mirror => sub {\n        my ($source, $dest, $jobs, $completion, $options) = @_;\n\n        my $drive_id = PVE::QemuServer::Drive::get_drive_id($source->{drive});\n\n        common_mirror_mock($source->{vmid}, $drive_id);\n    },\n    monitor => sub {\n        my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_;\n\n        if (\n            $fail_config->{block_job_monitor}\n            && $fail_config->{block_job_monitor} eq $completion\n        ) {\n            die \"block_job_monitor '$completion' error\\n\";\n        }\n        return;\n    },\n    qemu_drive_mirror_switch_to_active_mode => sub {\n        return;\n    },\n);\n\nmy $qemu_server_cpuconfig_module = Test::MockModule->new(\"PVE::QemuServer::CPUConfig\");\n$qemu_server_cpuconfig_module->mock(\n    get_cpu_from_running_vm => sub {\n        die \"invalid test: if you specify a custom CPU model you need to \"\n            . \"specify runningcpu as well\\n\"\n            if !defined($vm_status->{runningcpu});\n        return $vm_status->{runningcpu};\n    },\n);\n\nmy $qemu_server_helpers_module = Test::MockModule->new(\"PVE::QemuServer::Helpers\");\n$qemu_server_helpers_module->mock(\n    vm_running_locally => sub {\n        return $vm_status->{running} && !$vm_stop_executed;\n    },\n);\n\nmy $qemu_server_machine_module = Test::MockModule->new(\"PVE::QemuServer::Machine\");\n$qemu_server_machine_module->mock(\n    qemu_machine_pxe => sub {\n        die \"invalid test: no runningmachine specified\\n\"\n            if !defined($vm_status->{runningmachine});\n        return $vm_status->{runningmachine};\n    },\n    get_current_qemu_machine => sub {\n        die \"invalid test: no runningmachine specified\\n\"\n            if !defined($vm_status->{runningmachine});\n        return $vm_status->{runningmachine};\n    },\n);\n\nmy $qemu_server_network_module = Test::MockModule->new(\"PVE::QemuServer::Network\");\n$qemu_server_network_module->mock(\n    del_nets_bridge_fdb => sub { return; },\n    mon_cmd => sub {\n        my ($vmid, $command, %params) = @_;\n\n        if ($command eq 'qom-get') {\n            if (\n                $params{path} =~ m|^/machine/peripheral/net\\d+$|\n                && $params{property} eq 'host_mtu'\n            ) {\n                return 1500;\n            }\n            die \"mon_cmd (mocked) - implement me: $command for path '$params{path}' property\"\n                . \" '$params{property}'\";\n        }\n        die \"mon_cmd (mocked) - implement me: $command\";\n    },\n);\n\nmy $qemu_server_qmphelpers_module = Test::MockModule->new(\"PVE::QemuServer::QMPHelpers\");\n$qemu_server_qmphelpers_module->mock(\n    runs_at_least_qemu_version => sub {\n        return 1;\n    },\n);\n\nmy $ssh_info_module = Test::MockModule->new(\"PVE::SSHInfo\");\n$ssh_info_module->mock(\n    get_ssh_info => sub {\n        my ($node, $network_cidr) = @_;\n        return {\n            ip => '1.2.3.4',\n            name => $node,\n            network => $network_cidr,\n        };\n    },\n);\n\n$MigrationTest::Shared::storage_module->mock(\n    storage_migrate => sub {\n        my ($cfg, $volid, $target_sshinfo, $target_storeid, $opts, $logfunc) = @_;\n\n        die \"storage_migrate '$volid' error\\n\"\n            if $fail_config->{storage_migrate} && $fail_config->{storage_migrate} eq $volid;\n\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n        die \"invalid test: need to add entry for '$volid' to storage_migrate_map\\n\"\n            if $storeid ne $target_storeid && !defined($storage_migrate_map->{$volid});\n\n        my $target_volname = $storage_migrate_map->{$volid} // $opts->{target_volname}\n            // $volname;\n        my $target_volid = \"${target_storeid}:${target_volname}\";\n        MigrationTest::Shared::add_target_volid($target_volid);\n\n        return $target_volid;\n    },\n    vdisk_list => sub { # expects vmid to be set\n        my ($cfg, $storeid, $vmid, $vollist) = @_;\n\n        my @storeids = defined($storeid) ? ($storeid) : keys %{$source_vdisks};\n\n        my $res = {};\n        foreach my $storeid (@storeids) {\n            my $list_for_storeid = $source_vdisks->{$storeid};\n            my @list_for_vm = grep { $_->{vmid} eq $vmid } @{$list_for_storeid};\n            $res->{$storeid} = \\@list_for_vm;\n        }\n        return $res;\n    },\n    vdisk_free => sub {\n        my ($scfg, $volid) = @_;\n\n        PVE::Storage::parse_volume_id($volid);\n\n        die \"vdisk_free '$volid' error\\n\"\n            if defined($fail_config->{vdisk_free}) && $fail_config->{vdisk_free} eq $volid;\n\n        delete $source_volids->{$volid};\n    },\n    volume_size_info => sub {\n        my ($scfg, $volid) = @_;\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n        for my $v ($source_vdisks->{$storeid}->@*) {\n            return wantarray ? ($v->{size}, $v->{format}, $v->{used}, $v->{parent}) : $v->{size}\n                if $v->{volid} eq $volid;\n        }\n        die \"could not find '$volid' in mock 'source_vdisks'\\n\";\n    },\n);\n\n$MigrationTest::Shared::tools_module->mock(\n    get_host_address_family => sub {\n        die \"get_host_address_family (mocked) - implement me\\n\"; # currently no call should lead here\n    },\n    next_migrate_port => sub {\n        die \"next_migrate_port (mocked) - implement me\\n\"; # currently no call should lead here\n    },\n    run_command => sub {\n        my ($cmd_tail, %param) = @_;\n\n        my $cmd_msg = to_json($cmd_tail);\n\n        my $cmd = shift @{$cmd_tail};\n\n        if ($cmd =~ m@^(?:/usr/bin/)?ssh$@) {\n            while (scalar(@{$cmd_tail})) {\n                $cmd = shift @{$cmd_tail};\n                if ($cmd eq '/bin/true') {\n                    return 0;\n                } elsif ($cmd eq 'qm') {\n                    $cmd = shift @{$cmd_tail};\n                    if ($cmd eq 'start') {\n                        delete $expected_calls->{ssh_qm_start};\n\n                        delete $vm_status->{runningmachine};\n                        delete $vm_status->{runningcpu};\n\n                        my @options = (@{$cmd_tail});\n                        while (scalar(@options)) {\n                            my $opt = shift @options;\n                            if ($opt eq '--machine') {\n                                $vm_status->{runningmachine} = shift @options;\n                            } elsif ($opt eq '--force-cpu') {\n                                $vm_status->{runningcpu} = shift @options;\n                            }\n                        }\n\n                        return $MigrationTest::Shared::tools_module->original('run_command')->(\n                            [\n                                '/usr/bin/perl',\n                                \"-I${QM_LIB_PATH}\",\n                                \"-I${QM_LIB_PATH}/test\",\n                                \"${QM_LIB_PATH}/test/MigrationTest/QmMock.pm\",\n                                'start',\n                                @{$cmd_tail},\n                            ],\n                            %param,\n                        );\n\n                    } elsif ($cmd eq 'nbdstop') {\n                        delete $expected_calls->{ssh_nbdstop};\n                        return 0;\n                    } elsif ($cmd eq 'resume') {\n                        return 0;\n                    } elsif ($cmd eq 'unlock') {\n                        my $vmid = shift @{$cmd_tail};\n                        die \"unlocking wrong vmid: $vmid\\n\" if $vmid ne $test_vmid;\n                        PVE::QemuConfig->remove_lock($vmid);\n                        return 0;\n                    } elsif ($cmd eq 'stop') {\n                        return 0;\n                    }\n                    die \"run_command (mocked) ssh qm command - implement me: ${cmd_msg}\";\n                } elsif ($cmd eq 'pvesm') {\n                    $cmd = shift @{$cmd_tail};\n                    if ($cmd eq 'free') {\n                        my $volid = shift @{$cmd_tail};\n                        PVE::Storage::parse_volume_id($volid);\n                        return 1\n                            if $fail_config->{ssh_pvesm_free}\n                            && $fail_config->{ssh_pvesm_free} eq $volid;\n                        MigrationTest::Shared::remove_target_volid($volid);\n                        return 0;\n                    }\n                    die \"run_command (mocked) ssh pvesm command - implement me: ${cmd_msg}\";\n                }\n            }\n            die \"run_command (mocked) ssh command - implement me: ${cmd_msg}\";\n        }\n        die \"run_command (mocked) - implement me: ${cmd_msg}\";\n    },\n);\n\neval { PVE::QemuMigrate->migrate($test_target, undef, $test_vmid, $test_opts) };\nmy $error = $@;\n\nfile_set_contents(\"${RUN_DIR_PATH}/source_volids\", to_json($source_volids));\nfile_set_contents(\"${RUN_DIR_PATH}/vm_status\", to_json($vm_status));\nfile_set_contents(\"${RUN_DIR_PATH}/expected_calls\", to_json($expected_calls));\nfile_set_contents(\"${RUN_DIR_PATH}/log\", $current_log);\n\ndie $error if $error;\n\n1;\n"
  },
  {
    "path": "src/test/MigrationTest/QmMock.pm",
    "content": "package MigrationTest::QmMock;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse Test::MockModule;\n\nuse MigrationTest::Shared;\n\nuse PVE::API2::Qemu;\nuse PVE::Storage;\nuse PVE::Tools qw(file_set_contents file_get_contents);\n\nuse PVE::CLIHandler;\nuse base qw(PVE::CLIHandler);\n\nmy $RUN_DIR_PATH = $ENV{RUN_DIR_PATH} or die \"no RUN_DIR_PATH set\\n\";\n\nmy $target_volids = decode_json(file_get_contents(\"${RUN_DIR_PATH}/target_volids\"));\nmy $fail_config = decode_json(file_get_contents(\"${RUN_DIR_PATH}/fail_config\"));\nmy $migrate_params = decode_json(file_get_contents(\"${RUN_DIR_PATH}/migrate_params\"));\nmy $nodename = $migrate_params->{target};\n\nmy $kvm_exectued = 0;\nmy $forcemachine;\n\nsub setup_environment {\n    my $rpcenv = PVE::RPCEnvironment::init('MigrationTest::QmMock', 'cli');\n}\n\n# mock RPCEnvironment directly\n\nsub get_user {\n    return 'root@pam';\n}\n\nsub fork_worker {\n    my ($self, $dtype, $id, $user, $function, $background) = @_;\n    $function->(123456);\n    return '123456';\n}\n\n# mocked modules\n\nmy sub mocked_mon_cmd {\n    my ($vmid, $command, %params) = @_;\n\n    if ($command eq 'nbd-server-start') {\n        return;\n    } elsif ($command eq 'block-export-add') {\n        return;\n    } elsif ($command eq 'query-block') {\n        return [];\n    } elsif ($command eq 'qom-set') {\n        return;\n    }\n    die \"mon_cmd (mocked) - implement me: $command\";\n}\n\nmy $inotify_module = Test::MockModule->new(\"PVE::INotify\");\n$inotify_module->mock(\n    nodename => sub {\n        return $nodename;\n    },\n);\n\nmy $qemu_server_blockdev_module = Test::MockModule->new(\"PVE::QemuServer::Blockdev\");\n$qemu_server_blockdev_module->mock(\n    mon_cmd => \\&mocked_mon_cmd,\n);\n\nmy $qemu_server_helpers_module = Test::MockModule->new(\"PVE::QemuServer::Helpers\");\n$qemu_server_helpers_module->mock(\n    vm_running_locally => sub {\n        return $kvm_exectued;\n    },\n);\n\nour $qemu_server_machine_module = Test::MockModule->new(\"PVE::QemuServer::Machine\");\n$qemu_server_machine_module->mock(\n    get_current_qemu_machine => sub {\n        return wantarray ? ($forcemachine, 0) : $forcemachine;\n    },\n);\n\n# to make sure we get valid and predictable names\nmy $disk_counter = 10;\n\n$MigrationTest::Shared::storage_module->mock(\n    vdisk_alloc => sub {\n        my ($cfg, $storeid, $vmid, $fmt, $name, $size) = @_;\n\n        die \"vdisk_alloc (mocked) - name is not expected to be set - implement me\\n\"\n            if defined($name);\n\n        my $name_without_extension = \"vm-${vmid}-disk-${disk_counter}\";\n        $disk_counter++;\n\n        my $volid;\n        my $scfg = PVE::Storage::storage_config($cfg, $storeid);\n        if ($scfg->{path}) {\n            $volid = \"${storeid}:${vmid}/${name_without_extension}.${fmt}\";\n        } else {\n            $volid = \"${storeid}:${name_without_extension}\";\n        }\n\n        PVE::Storage::parse_volume_id($volid);\n\n        die \"vdisk_alloc '$volid' error\\n\"\n            if $fail_config->{vdisk_alloc}\n            && $fail_config->{vdisk_alloc} eq $volid;\n\n        MigrationTest::Shared::add_target_volid($volid);\n\n        return $volid;\n    },\n);\n\n$MigrationTest::Shared::qemu_server_module->mock(\n    config_to_command => sub {\n        return ['mocked_kvm_command'];\n    },\n    mon_cmd => \\&mocked_mon_cmd,\n    nodename => sub {\n        return $nodename;\n    },\n    run_command => sub {\n        my ($cmd_full, %param) = @_;\n\n        my $cmd_msg = to_json($cmd_full);\n\n        my $cmd = shift @{$cmd_full};\n\n        if ($cmd eq '/bin/systemctl') {\n            return;\n        } elsif ($cmd eq 'mocked_kvm_command') {\n            $kvm_exectued = 1;\n            return 0;\n        }\n        die \"run_command (mocked) - implement me: ${cmd_msg}\";\n    },\n    vm_migrate_alloc_nbd_disks => sub {\n        my $nbd =\n            $MigrationTest::Shared::qemu_server_module->original('vm_migrate_alloc_nbd_disks')\n            ->(@_);\n        file_set_contents(\"${RUN_DIR_PATH}/nbd_info\", to_json($nbd));\n        return $nbd;\n    },\n    vm_start_nolock => sub {\n        my ($storecfg, $vmid, $conf, $params, $migrate_opts) = @_;\n        $forcemachine = $params->{forcemachine}\n            or die \"mocked vm_start_nolock - expected 'forcemachine' parameter\\n\";\n        $MigrationTest::Shared::qemu_server_module->original('vm_start_nolock')->(@_);\n    },\n);\n\nour $cmddef = {\n    start => [\"PVE::API2::Qemu\", 'vm_start', ['vmid'], { node => $nodename }],\n};\n\nMigrationTest::QmMock->run_cli_handler();\n\n1;\n"
  },
  {
    "path": "src/test/MigrationTest/Shared.pm",
    "content": "package MigrationTest::Shared;\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse Test::MockModule;\nuse Socket qw(AF_INET);\n\nuse PVE::QemuConfig;\nuse PVE::Tools qw(file_set_contents file_get_contents lock_file_full);\n\nmy $RUN_DIR_PATH = $ENV{RUN_DIR_PATH} or die \"no RUN_DIR_PATH set\\n\";\n\nmy $storage_config = decode_json(file_get_contents(\"${RUN_DIR_PATH}/storage_config\"));\nmy $replication_config = decode_json(file_get_contents(\"${RUN_DIR_PATH}/replication_config\"));\nmy $fail_config = decode_json(file_get_contents(\"${RUN_DIR_PATH}/fail_config\"));\nmy $migrate_params = decode_json(file_get_contents(\"${RUN_DIR_PATH}/migrate_params\"));\nmy $test_vmid = $migrate_params->{vmid};\n\n# helpers\n\nsub add_target_volid {\n    my ($volid) = @_;\n\n    PVE::Storage::parse_volume_id($volid);\n\n    lock_file_full(\n        \"${RUN_DIR_PATH}/target_volids.lock\",\n        undef,\n        0,\n        sub {\n            my $target_volids = decode_json(file_get_contents(\"${RUN_DIR_PATH}/target_volids\"));\n            die \"target volid already present \" if defined($target_volids->{$volid});\n            $target_volids->{$volid} = 1;\n            file_set_contents(\"${RUN_DIR_PATH}/target_volids\", to_json($target_volids));\n        },\n    );\n    die $@ if $@;\n}\n\nsub remove_target_volid {\n    my ($volid) = @_;\n\n    PVE::Storage::parse_volume_id($volid);\n\n    lock_file_full(\n        \"${RUN_DIR_PATH}/target_volids.lock\",\n        undef,\n        0,\n        sub {\n            my $target_volids = decode_json(file_get_contents(\"${RUN_DIR_PATH}/target_volids\"));\n            die \"target volid does not exist \" if !defined($target_volids->{$volid});\n            delete $target_volids->{$volid};\n            file_set_contents(\"${RUN_DIR_PATH}/target_volids\", to_json($target_volids));\n        },\n    );\n    die $@ if $@;\n}\n\nmy $mocked_cfs_read_file = sub {\n    my ($file) = @_;\n\n    if ($file eq 'datacenter.cfg') {\n        return {};\n    } elsif ($file eq 'replication.cfg') {\n        return $replication_config;\n    }\n    die \"cfs_read_file (mocked) - implement me: $file\\n\";\n};\n\n# mocked modules\n\nour $cgroup_module = Test::MockModule->new(\"PVE::CGroup\");\n$cgroup_module->mock(\n    cgroup_mode => sub {\n        return 2;\n    },\n);\n\nour $cluster_module = Test::MockModule->new(\"PVE::Cluster\");\n$cluster_module->mock(\n    cfs_read_file => $mocked_cfs_read_file,\n    check_cfs_quorum => sub {\n        return 1;\n    },\n);\n\nour $mapping_usb_module = Test::MockModule->new(\"PVE::Mapping::USB\");\n$mapping_usb_module->mock(\n    config => sub {\n        return {};\n    },\n);\n\nour $mapping_pci_module = Test::MockModule->new(\"PVE::Mapping::PCI\");\n$mapping_pci_module->mock(\n    config => sub {\n        return {};\n    },\n);\n\nour $mapping_dir_module = Test::MockModule->new(\"PVE::Mapping::Dir\");\n$mapping_dir_module->mock(\n    config => sub {\n        return {};\n    },\n);\n\nour $ha_config_module = Test::MockModule->new(\"PVE::HA::Config\");\n$ha_config_module->mock(\n    vm_is_ha_managed => sub {\n        return 0;\n    },\n);\n\nour $qemu_config_module = Test::MockModule->new(\"PVE::QemuConfig\");\n$qemu_config_module->mock(\n    assert_config_exists_on_node => sub {\n        return;\n    },\n    load_config => sub {\n        my ($class, $vmid, $node) = @_;\n        die \"trying to load wrong config: '$vmid'\\n\" if $vmid ne $test_vmid;\n        return decode_json(file_get_contents(\"${RUN_DIR_PATH}/vm_config\"));\n    },\n    lock_config => sub { # no use locking here because lock is local to node\n        my ($self, $vmid, $code, @param) = @_;\n        return $code->(@param);\n    },\n    write_config => sub {\n        my ($class, $vmid, $conf) = @_;\n        die \"trying to write wrong config: '$vmid'\\n\" if $vmid ne $test_vmid;\n        file_set_contents(\"${RUN_DIR_PATH}/vm_config\", to_json($conf));\n    },\n);\n\nour $qemu_migrate_helpers_module = Test::MockModule->new(\"PVE::QemuMigrate::Helpers\");\n$qemu_migrate_helpers_module->mock(\n    set_migration_caps => sub {\n        return;\n    },\n);\n\nour $qemu_server_cloudinit_module = Test::MockModule->new(\"PVE::QemuServer::Cloudinit\");\n$qemu_server_cloudinit_module->mock(\n    generate_cloudinitconfig => sub {\n        return;\n    },\n);\n\nour $qemu_server_module = Test::MockModule->new(\"PVE::QemuServer\");\n$qemu_server_module->mock(\n    clear_reboot_request => sub {\n        return 1;\n    },\n    vm_stop_cleanup => sub {\n        return;\n    },\n);\n\nour $qemu_server_ovmf_module = Test::MockModule->new(\"PVE::QemuServer::OVMF\");\n$qemu_server_ovmf_module->mock(\n    get_efivars_size => sub {\n        return 128 * 1024;\n    },\n);\n\nour $replication_module = Test::MockModule->new(\"PVE::Replication\");\n$replication_module->mock(\n    run_replication => sub {\n        die \"run_replication error\" if $fail_config->{run_replication};\n\n        my $vm_config = PVE::QemuConfig->load_config($test_vmid);\n        return PVE::QemuConfig->get_replicatable_volumes(\n            $storage_config, $test_vmid, $vm_config,\n        );\n    },\n);\n\nour $replication_config_module = Test::MockModule->new(\"PVE::ReplicationConfig\");\n$replication_config_module->mock(\n    cfs_read_file => $mocked_cfs_read_file,\n);\n\nour $safe_syslog_module = Test::MockModule->new(\"PVE::SafeSyslog\");\n$safe_syslog_module->mock(\n    initlog => sub { },\n    syslog => sub { },\n);\n\nour $storage_module = Test::MockModule->new(\"PVE::Storage\");\n$storage_module->mock(\n    activate_volumes => sub {\n        return 1;\n    },\n    deactivate_volumes => sub {\n        return 1;\n    },\n    config => sub {\n        return $storage_config;\n    },\n    get_bandwidth_limit => sub {\n        return 123456;\n    },\n    cfs_read_file => $mocked_cfs_read_file,\n);\n\nour $storage_plugin_module = Test::MockModule->new(\"PVE::Storage::Plugin\");\n$storage_plugin_module->mock(\n    cluster_lock_storage => sub {\n        my ($class, $storeid, $shared, $timeout, $func, @param) = @_;\n\n        mkdir \"${RUN_DIR_PATH}/lock\";\n\n        my $path = \"${RUN_DIR_PATH}/lock/pve-storage-${storeid}\";\n        return PVE::Tools::lock_file($path, $timeout, $func, @param);\n    },\n);\n\nour $systemd_module = Test::MockModule->new(\"PVE::Systemd\");\n$systemd_module->mock(\n    wait_for_unit_removed => sub {\n        return;\n    },\n    enter_systemd_scope => sub {\n        return;\n    },\n);\n\nmy $migrate_port_counter = 60000;\n\nour $tools_module = Test::MockModule->new(\"PVE::Tools\");\n$tools_module->mock(\n    get_host_address_family => sub {\n        return AF_INET;\n    },\n    next_migrate_port => sub {\n        return $migrate_port_counter++;\n    },\n);\n\n1;\n"
  },
  {
    "path": "src/test/cfg2cmd/README.adoc",
    "content": "QemuServer Config 2 Command Test\n================================\nThomas Lamprecht <t.lamprecht@proxmox.com>\n\nOverview\n--------\n\nThis is a relatively simple configuration to command test program.\nIt's main goals are to better enforce stability of commands, thus reducing\nthe likelihood that, for example, a migration breaking change which forgot to\nbump/check the KVM/QEMU version, slips through\n\nFurther you get a certain regression and functional test coverage. You get a\nsafety net against breaking older or not often (manual) tested setups and\nfeatures.\n\nNOTE: The safety net is only as good as the test count *and* quality.\n\n\nTest Specification\n------------------\n\nA single test consists of two files, the input VM config `FILE.conf` and the\nexpected output command `FILE.conf.cmd`\n\nInput\n~~~~~\n\nThe `FILE.conf` are standard Proxmox VE VM configuration files, so you can just\ncopy over a config file from `/etc/pve/qemu-server` to add a configuration you\nwant to have tested.\n\nOutput\n~~~~~~\n\nFor the expected output `FILE.conf.cmd` we check the KVM/QEMU command produced.\nAs a single long line would be pretty hard to check for (problematic) changes\nby humans, we use a pretty format, i.e., where each key value pair is on it's\nown line. With this approach we can just diff expected and actual command and\none can pin point pretty fast in which component (e.g., net, drives, CPU, ...)\nthe issue is, if any. Such an output would look like:\n\n----\n/usr/bin/kvm \\\n  -id 101 \\\n  -name vm101 \\\n  ...\n----\n\nTIP: If the expected output file does not exist we have nothing to check, but\nfor convenience we will write it out. This should happen from clean code, and\nthe result should not get applied blindly, but only after a (quick) sanity\ncheck.\n\n\nEnvironment\n~~~~~~~~~~~\n\nIt makes sense to have a stable and controlled environment for tests, thus you\none can use the 'description' in VM configurations to control this. The\ndescription consists of all lines beginning with a '#' as first non-whitespace\ncharacter. Any environment variable follows the following format:\n\n----\n# NAME: VALUE\n... rest of config...\n----\n\nThere are the following variables you can control:\n\n* *TEST*: a one line description for your test, gets outputted one testing and\n  should described in a short way any specialty about this specific test,\n  i.e., what does this test wants to ensure.\n\n* *QEMU_VERSION*: the version we fake for this test, if not set we use the\n  actual one installed on the host.\n\n* *HOST_ARCH*: the architecture we should fake for the test (aarch64 or x86_64),\n  defaults to `x86_64` to allow making this optional and still guarantee\n  stable tests\n\nThe storage environment is currently hardcoded in the test code, you can\nextend it there if it's needed.\n\n// vim: noai:tw=78\n"
  },
  {
    "path": "src/test/cfg2cmd/aarch64/simple-arm-host.conf",
    "content": "# TEST: Simple test for a basic configuration with aarch64 set as the host architecture\n# HOST_ARCH: aarch64\nbootdisk: scsi0\nbios: ovmf\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nefidisk0: local:8006/vm-8006-disk-1.qcow2,efitype=4m\n"
  },
  {
    "path": "src/test/cfg2cmd/aarch64/simple-arm-host.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//AAVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-1.qcow2\",\"node-name\":\"e4abf08bba72d3a54b87a58d2f50906\",\"read-only\":false},\"node-name\":\"f4abf08bba72d3a54b87a58d2f50906\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu cortex-a57 \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pcie.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pcie.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'usb-ehci,id=ehci,bus=pcie.0,addr=0x1' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'usb-kbd,id=keyboard,bus=ehci.0,port=2' \\\n  -device 'virtio-gpu,id=vga,bus=pcie.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pcie.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pcie.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"ecd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"fcd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pcie.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=virt+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/aarch64/simple-arm.conf",
    "content": "# TEST: Simple test for a basic configuration with aarch64 set as architecture\narch: aarch64\nbootdisk: scsi0\nbios: ovmf\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nefidisk0: local:8006/vm-8006-disk-1.qcow2,efitype=4m\n"
  },
  {
    "path": "src/test/cfg2cmd/aarch64/simple-arm.conf.cmd",
    "content": "/usr/bin/qemu-system-aarch64 \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//AAVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-1.qcow2\",\"node-name\":\"e4abf08bba72d3a54b87a58d2f50906\",\"read-only\":false},\"node-name\":\"f4abf08bba72d3a54b87a58d2f50906\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu cortex-a57 \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pcie.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pcie.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'usb-ehci,id=ehci,bus=pcie.0,addr=0x1' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'usb-kbd,id=keyboard,bus=ehci.0,port=2' \\\n  -device 'virtio-gpu,id=vga,bus=pcie.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pcie.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pcie.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"ecd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"fcd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pcie.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,accel=tcg,type=virt+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/aarch64/simple-x86-on-arm-host.conf",
    "content": "# TEST: Simple test for a basic config with aarch64 set as the host arch and x86_64 as the VM arch\n# HOST_ARCH: aarch64\narch: x86_64\nbootdisk: scsi0\nbios: ovmf\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nefidisk0: local:8006/vm-8006-disk-1.qcow2,efitype=4m\n"
  },
  {
    "path": "src/test/cfg2cmd/aarch64/simple-x86-on-arm-host.conf.cmd",
    "content": "/usr/bin/qemu-system-x86_64\n-id 8006\n-name 'simple,debug-threads=on'\n-no-shutdown\n-chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off'\n-mon 'chardev=qmp,mode=control'\n-chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000'\n-mon 'chardev=qmp-event,mode=control'\n-pidfile /var/run/qemu-server/8006.pid\n-daemonize\n-smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465'\n-object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}'\n-blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE_4M.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}'\n-blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-1.qcow2\",\"node-name\":\"e4abf08bba72d3a54b87a58d2f50906\",\"read-only\":false},\"node-name\":\"f4abf08bba72d3a54b87a58d2f50906\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}'\n-smp '3,sockets=1,cores=3,maxcpus=3'\n-nodefaults\n-boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg'\n-vnc 'unix:/var/run/qemu-server/8006.vnc,password=on'\n-cpu qemu64\n-m 768\n-object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}'\n-global 'PIIX4_PM.disable_s3=1'\n-global 'PIIX4_PM.disable_s4=1'\n-device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e'\n-device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f'\n-device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8'\n-device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2'\n-device 'usb-tablet,id=tablet,bus=uhci.0,port=1'\n-device 'VGA,id=vga,bus=pci.0,addr=0x2'\n-device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on'\n-iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff'\n-device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200'\n-device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5'\n-blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"ecd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"fcd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}'\n-device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on'\n-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown'\n-device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500'\n-machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,accel=tcg,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/aio.conf",
    "content": "scsi0: local:8006/vm-8006-disk-0.raw,discard=on,size=104858K,aio=threads\nscsi1: local:8006/vm-8006-disk-1.raw,discard=on,size=104858K,aio=native\nscsi2: local:8006/vm-8006-disk-2.raw,discard=on,size=104858K,aio=io_uring\nscsi3: local:8006/vm-8006-disk-3.raw,discard=on,size=104858K\nscsi4: cifs-store:8006/vm-8006-disk-4.raw,discard=on,size=104858K\nscsi5: cifs-store:8006/vm-8006-disk-5.raw,discard=on,size=104858K,aio=io_uring\nscsi6: krbd-store:vm-8006-disk-6,discard=on,size=104858K\nscsi7: krbd-store:vm-8006-disk-7,discard=on,size=104858K,aio=io_uring\nscsi8: krbd-store:vm-8006-disk-8,discard=on,size=104858K,cache=writeback\nscsi9: krbd-store:vm-8006-disk-9,discard=on,size=104858K,cache=writeback,aio=io_uring\nscsi10: rbd-store:vm-8006-disk-8,discard=on,size=104858K\nscsi11: rbd-store:vm-8006-disk-8,discard=on,size=104858K,aio=io_uring\nscsi12: lvm-store:vm-8006-disk-9,discard=on,size=104858K\nscsi13: lvm-store:vm-8006-disk-9,discard=on,size=104858K,aio=io_uring\n"
  },
  {
    "path": "src/test/cfg2cmd/aio.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi4\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi6\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi7\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi8\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi9\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi10\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi11\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi12\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi13\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.raw\",\"node-name\":\"e3b2553803d55d43b9986a0aac3e9a7\",\"read-only\":false},\"node-name\":\"f3b2553803d55d43b9986a0aac3e9a7\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-1.raw\",\"node-name\":\"e08707d013893852b3d4d42301a4298\",\"read-only\":false},\"node-name\":\"f08707d013893852b3d4d42301a4298\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-2.raw\",\"node-name\":\"edb0854bba55e8b2544ad937c9f5afc\",\"read-only\":false},\"node-name\":\"fdb0854bba55e8b2544ad937c9f5afc\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-3.raw\",\"node-name\":\"e9c170cb9491763cad3f31718205efc\",\"read-only\":false},\"node-name\":\"f9c170cb9491763cad3f31718205efc\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/images/8006/vm-8006-disk-4.raw\",\"node-name\":\"ea34ecc24c40da0d53420ef344ced37\",\"read-only\":false},\"node-name\":\"fa34ecc24c40da0d53420ef344ced37\",\"read-only\":false},\"node-name\":\"drive-scsi4\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi4\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=4,drive=drive-scsi4,id=scsi4,device_id=drive-scsi4,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/images/8006/vm-8006-disk-5.raw\",\"node-name\":\"e39cacf47a4f4877072601505d90949\",\"read-only\":false},\"node-name\":\"f39cacf47a4f4877072601505d90949\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-6\",\"node-name\":\"e7db1ee70981087e4a2861bc7da417b\",\"read-only\":false},\"node-name\":\"f7db1ee70981087e4a2861bc7da417b\",\"read-only\":false},\"node-name\":\"drive-scsi6\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi6\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=6,drive=drive-scsi6,id=scsi6,device_id=drive-scsi6,write-cache=on' \\\n  -device 'lsi,id=scsihw1,bus=pci.0,addr=0x6' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-7\",\"node-name\":\"e2d2deac808301140a96c862fe3ea85\",\"read-only\":false},\"node-name\":\"f2d2deac808301140a96c862fe3ea85\",\"read-only\":false},\"node-name\":\"drive-scsi7\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi7\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=0,drive=drive-scsi7,id=scsi7,device_id=drive-scsi7,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-8\",\"node-name\":\"e9796b73db57b8943746ede7d0d3060\",\"read-only\":false},\"node-name\":\"f9796b73db57b8943746ede7d0d3060\",\"read-only\":false},\"node-name\":\"drive-scsi8\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi8\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=1,drive=drive-scsi8,id=scsi8,device_id=drive-scsi8,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-9\",\"node-name\":\"efa538892acc012edbdc5810035bf7d\",\"read-only\":false},\"node-name\":\"ffa538892acc012edbdc5810035bf7d\",\"read-only\":false},\"node-name\":\"drive-scsi9\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi9\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=2,drive=drive-scsi9,id=scsi9,device_id=drive-scsi9,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"rbd\",\"image\":\"vm-8006-disk-8\",\"node-name\":\"e6f4cbffa741d16bba69304eb2800ef\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"f6f4cbffa741d16bba69304eb2800ef\",\"read-only\":false},\"node-name\":\"drive-scsi10\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi10\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=3,drive=drive-scsi10,id=scsi10,device_id=drive-scsi10,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"rbd\",\"image\":\"vm-8006-disk-8\",\"node-name\":\"e42375c54de70f5f4be966d98c90255\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"f42375c54de70f5f4be966d98c90255\",\"read-only\":false},\"node-name\":\"drive-scsi11\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi11\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=4,drive=drive-scsi11,id=scsi11,device_id=drive-scsi11,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-9\",\"node-name\":\"ed7b2c9e0133619fcf6cb8ce5903502\",\"read-only\":false},\"node-name\":\"fd7b2c9e0133619fcf6cb8ce5903502\",\"read-only\":false},\"node-name\":\"drive-scsi12\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi12\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=5,drive=drive-scsi12,id=scsi12,device_id=drive-scsi12,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-9\",\"node-name\":\"ed85420a880203ca1401d00a8edf132\",\"read-only\":false},\"node-name\":\"fd85420a880203ca1401d00a8edf132\",\"read-only\":false},\"node-name\":\"drive-scsi13\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi13\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=6,drive=drive-scsi13,id=scsi13,device_id=drive-scsi13,write-cache=on' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/bootorder-empty.conf",
    "content": "# TEST: Test for an empty boot parameter producing no bootindices either\ncores: 3\nboot: \nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi4: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvirtio0: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvirtio1: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/bootorder-empty.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi4\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object 'iothread,id=iothread-virtio0' \\\n  -object '{\"id\":\"throttle-drive-virtio0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object 'iothread,id=iothread-virtio1' \\\n  -object '{\"id\":\"throttle-drive-virtio1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"e6bf62e20f6c14a2c19bd6f1f5ac36c\",\"read-only\":false},\"node-name\":\"f6bf62e20f6c14a2c19bd6f1f5ac36c\",\"read-only\":false},\"node-name\":\"drive-scsi4\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi4\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=4,drive=drive-scsi4,id=scsi4,device_id=drive-scsi4,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"edd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"fdd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"drive-virtio0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio0\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio0,id=virtio0,bus=pci.0,addr=0xa,iothread=iothread-virtio0,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"eeb683fb9c516c1a8707c917f0d7a38\",\"read-only\":false},\"node-name\":\"feb683fb9c516c1a8707c917f0d7a38\",\"read-only\":false},\"node-name\":\"drive-virtio1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio1\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio1,id=virtio1,bus=pci.0,addr=0xb,iothread=iothread-virtio1,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/bootorder-legacy.conf",
    "content": "# TEST: Test for a specific bootorder given by legacy 'boot' value\ncores: 3\nboot: ndca\nbootdisk: virtio1\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi4: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvirtio0: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvirtio1: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/bootorder-legacy.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi4\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object 'iothread,id=iothread-virtio0' \\\n  -object '{\"id\":\"throttle-drive-virtio0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object 'iothread,id=iothread-virtio1' \\\n  -object '{\"id\":\"throttle-drive-virtio1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"e6bf62e20f6c14a2c19bd6f1f5ac36c\",\"read-only\":false},\"node-name\":\"f6bf62e20f6c14a2c19bd6f1f5ac36c\",\"read-only\":false},\"node-name\":\"drive-scsi4\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi4\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=4,drive=drive-scsi4,id=scsi4,device_id=drive-scsi4,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"edd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"fdd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"drive-virtio0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio0\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio0,id=virtio0,bus=pci.0,addr=0xa,iothread=iothread-virtio0,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"eeb683fb9c516c1a8707c917f0d7a38\",\"read-only\":false},\"node-name\":\"feb683fb9c516c1a8707c917f0d7a38\",\"read-only\":false},\"node-name\":\"drive-virtio1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio1\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio1,id=virtio1,bus=pci.0,addr=0xb,iothread=iothread-virtio1,bootindex=302,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=100,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/bootorder.conf",
    "content": "# TEST: Test for a specific bootorder given by 'boot: order=' property\ncores: 3\nboot: order=virtio1;net0;scsi4;ide2\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi4: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvirtio0: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvirtio1: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/bootorder.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi4\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object 'iothread,id=iothread-virtio0' \\\n  -object '{\"id\":\"throttle-drive-virtio0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object 'iothread,id=iothread-virtio1' \\\n  -object '{\"id\":\"throttle-drive-virtio1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=103' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"e6bf62e20f6c14a2c19bd6f1f5ac36c\",\"read-only\":false},\"node-name\":\"f6bf62e20f6c14a2c19bd6f1f5ac36c\",\"read-only\":false},\"node-name\":\"drive-scsi4\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi4\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=4,drive=drive-scsi4,id=scsi4,device_id=drive-scsi4,bootindex=102,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"edd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"fdd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"drive-virtio0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio0\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio0,id=virtio0,bus=pci.0,addr=0xa,iothread=iothread-virtio0,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"eeb683fb9c516c1a8707c917f0d7a38\",\"read-only\":false},\"node-name\":\"feb683fb9c516c1a8707c917f0d7a38\",\"read-only\":false},\"node-name\":\"drive-virtio1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio1\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio1,id=virtio1,bus=pci.0,addr=0xb,iothread=iothread-virtio1,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=101,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/cputype-icelake-client-deprecation.conf",
    "content": "# TEST: test CPU type deprecation for Icelake-Client (never existed in the wild)\nbootdisk: scsi0\ncores: 2\ncpu: Icelake-Client\nide2: none,media=cdrom\nmemory: 768\nname: simple\nostype: l26\nscsi0: local:8006/base-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/cputype-icelake-client-deprecation.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu 'Icelake-Server,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,vendor=GenuineIntel' \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/base-8006-disk-0.qcow2\",\"node-name\":\"e417d5947e69c5890b1e3ddf8a68167\",\"read-only\":false},\"node-name\":\"f417d5947e69c5890b1e3ddf8a68167\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/custom-cpu-model-defaults.conf",
    "content": "# TEST: Check if custom CPU models are resolving defaults correctly\ncores: 3\ncpu: custom-alldefault\nname: customcpu-defaults\nnuma: 0\nostype: l26\nsmbios1: uuid=2ea3f676-dfa5-11e9-ae82-c721e12f3fce\nsockets: 1\n"
  },
  {
    "path": "src/test/cfg2cmd/custom-cpu-model-defaults.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'customcpu-defaults,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=2ea3f676-dfa5-11e9-ae82-c721e12f3fce' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/custom-cpu-model-host-phys-bits.conf",
    "content": "# TEST: Check if custom CPU models are resolved correctly\n# EXPECT_WARN: warning: CPU flag/setting '-kvm_pv_unhalt' (set by custom CPU model) overwrites '+kvm_pv_unhalt' (set by PVE; to improve Linux guest spinlock performance)\ncores: 3\ncpu: custom-qemu64,phys-bits=host\nname: customcpu\nnuma: 0\nostype: win10\nsmbios1: uuid=2ea3f676-dfa5-11e9-ae82-c721e12f3fcf\nsockets: 1\n"
  },
  {
    "path": "src/test/cfg2cmd/custom-cpu-model-host-phys-bits.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'customcpu,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=2ea3f676-dfa5-11e9-ae82-c721e12f3fcf' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'athlon,+aes,+avx,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vendor_id=testvend,hv_vpindex,+kvm_pv_eoi,-kvm_pv_unhalt,vendor=AuthenticAMD,host-phys-bits=true' \\\n  -m 512 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2,edid=off' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -rtc 'driftfix=slew,base=localtime' \\\n  -machine 'hpet=off,type=pc-i440fx-5.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/custom-cpu-model.conf",
    "content": "# TEST: Check if custom CPU models are resolved correctly\n# EXPECT_WARN: warning: CPU flag/setting '-kvm_pv_unhalt' (set by custom CPU model) overwrites '+kvm_pv_unhalt' (set by PVE; to improve Linux guest spinlock performance)\ncores: 3\ncpu: custom-qemu64,flags=+virt-ssbd\nname: customcpu\nnuma: 0\nostype: win10\nsmbios1: uuid=2ea3f676-dfa5-11e9-ae82-c721e12f3fcf\nsockets: 1\n"
  },
  {
    "path": "src/test/cfg2cmd/custom-cpu-model.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'customcpu,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=2ea3f676-dfa5-11e9-ae82-c721e12f3fcf' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'athlon,+aes,+avx,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vendor_id=testvend,hv_vpindex,+kvm_pv_eoi,-kvm_pv_unhalt,vendor=AuthenticAMD,+virt-ssbd,phys-bits=40' \\\n  -m 512 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2,edid=off' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -rtc 'driftfix=slew,base=localtime' \\\n  -machine 'hpet=off,type=pc-i440fx-5.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-ovmf-without-efidisk.conf",
    "content": "# TEST: Test VM with OVMF/EFI but no persistent efidisk for backward compat.\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-ovmf-without-efidisk.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/tmp/8006-ovmf.fd\",\"node-name\":\"e5b5f7a29888341a35f0f1428e70ba5\",\"read-only\":false},\"node-name\":\"f5b5f7a29888341a35f0f1428e70ba5\",\"read-only\":false,\"size\":131072},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-raw-old.conf",
    "content": "# TEST: Test raw efidisk size parameter on old version\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nmachine: pc-i440fx-4.1+pve0\nefidisk0: local:100/vm-100-disk-0.raw\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-raw-old.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \\\n  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=raw,file=/var/lib/vz/images/100/vm-100-disk-0.raw' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc-i440fx-4.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-raw-template.conf",
    "content": "# TEST: Test raw efidisk size parameter\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nefidisk0: local:100/base-100-disk-0.raw\ntemplate: 1\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-raw-template.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/base-100-disk-0.raw\",\"node-name\":\"e2ab65c8ec567acbeb645244f6c4982\",\"read-only\":true},\"node-name\":\"f2ab65c8ec567acbeb645244f6c4982\",\"read-only\":true,\"size\":131072},\"node-name\":\"drive-efidisk0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vga none \\\n  -nographic \\\n  -cpu qemu64 \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,accel=tcg,type=pc+pve0' \\\n  -snapshot\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-raw.conf",
    "content": "# TEST: Test raw efidisk size parameter\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nefidisk0: local:100/vm-100-disk-0.raw\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-raw.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-0.raw\",\"node-name\":\"e1175f2a490414e7c53337589fde17a\",\"read-only\":false},\"node-name\":\"f1175f2a490414e7c53337589fde17a\",\"read-only\":false,\"size\":131072},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-secboot-and-tpm-q35.conf",
    "content": "# TEST: Test newer 4MB efidisk with secureboot, smm enforce and a TPM device on Q35\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nmachine: q35\nefidisk0: local:100/vm-100-disk-0.raw,efitype=4m,pre-enrolled-keys=1,size=528K\ntpmstate0: local:108/vm-100-disk-1.raw,size=4M,version=v2.0\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-secboot-and-tpm-q35.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE_4M.secboot.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-0.raw\",\"node-name\":\"e1175f2a490414e7c53337589fde17a\",\"read-only\":false},\"node-name\":\"f1175f2a490414e7c53337589fde17a\",\"read-only\":false,\"size\":540672},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -chardev 'socket,id=tpmchar,path=/var/run/qemu-server/8006.swtpm' \\\n  -tpmdev 'emulator,id=tpmdev,chardev=tpmchar' \\\n  -device 'tpm-tis,tpmdev=tpmdev' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-secboot-and-tpm.conf",
    "content": "# TEST: Test newer 4MB efidisk with secureboot and a TPM device\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nefidisk0: local:100/vm-100-disk-0.raw,efitype=4m,pre-enrolled-keys=1,size=528K\ntpmstate0: local:108/vm-100-disk-1.raw,size=4M,version=v2.0\n"
  },
  {
    "path": "src/test/cfg2cmd/efi-secboot-and-tpm.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE_4M.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-0.raw\",\"node-name\":\"e1175f2a490414e7c53337589fde17a\",\"read-only\":false},\"node-name\":\"f1175f2a490414e7c53337589fde17a\",\"read-only\":false,\"size\":540672},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -chardev 'socket,id=tpmchar,path=/var/run/qemu-server/8006.swtpm' \\\n  -tpmdev 'emulator,id=tpmdev,chardev=tpmchar' \\\n  -device 'tpm-tis,tpmdev=tpmdev' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/efidisk-on-rbd.conf",
    "content": "# TEST: Config with efi disk on RBD is very slow without cache - #3329\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: rbd-store:vm-100-disk-1,size=128K\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e688\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13a\n"
  },
  {
    "path": "src/test/cfg2cmd/efidisk-on-rbd.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e688' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"rbd\",\"image\":\"vm-100-disk-1\",\"node-name\":\"eeb8f022b5551ad1d795611f112c767\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"feb8f022b5551ad1d795611f112c767\",\"read-only\":false,\"size\":131072},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=512M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13a' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/i440fx-viommu-intel.conf",
    "content": "# EXPECT_ERROR: to use Intel vIOMMU please set the machine type to q35\nmachine: pc,viommu=intel\n"
  },
  {
    "path": "src/test/cfg2cmd/i440fx-viommu-virtio.conf",
    "content": "machine: pc,viommu=virtio\n"
  },
  {
    "path": "src/test/cfg2cmd/i440fx-viommu-virtio.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device virtio-iommu-pci \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/i440fx-win10-hostpci.conf",
    "content": "# TEST: Config with i440fx, NUMA, hostpci passthrough, EFI & Windows\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: 0f:f2.0\nmachine: pc\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: win10\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \\\n  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'vfio-pci,host=0000:0f:f2.0,id=hostpci0,bus=pci.0,addr=0x10' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -rtc 'driftfix=slew,base=localtime' \\\n  -machine 'hpet=off,type=pc-i440fx-5.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/ide-no-media-error.conf",
    "content": "# TEST: Config that doesn't specify an explicit `media` for an ISO drive\n# EXPECT_ERROR: ide3: explicit media parameter is required for iso images\nbootdisk: scsi0\ncores: 2\nide0: cifs-store:iso/zero.iso,media=cdrom,size=112M\nide1: cifs-store:iso/one.iso,media=cdrom,size=112M\nide2: cifs-store:iso/two.iso,media=disk,size=112M\nide3: cifs-store:iso/three.iso,size=112M\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsi0: local:100/vm-100-disk-2.qcow2,size=10G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/ide.conf",
    "content": "# TEST: Config with default machine type, Linux & four IDE CD-ROMs\nbootdisk: scsi0\ncores: 2\nide0: local:iso/zero.iso,media=cdrom,size=112M\nide1: cifs-store:iso/one.iso,media=cdrom,size=112M\nide2: cifs-store:iso/two.iso,media=cdrom,size=112M\nide3: cifs-store:iso/three.iso,media=cdrom,size=112M\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsi0: local:100/vm-100-disk-2.qcow2,size=10G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/ide.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-ide0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-ide1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-ide2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-ide3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/var/lib/vz/template/iso/zero.iso\",\"node-name\":\"e19e15bf93b8cf09e2a5d1669648165\",\"read-only\":true},\"node-name\":\"f19e15bf93b8cf09e2a5d1669648165\",\"read-only\":true},\"node-name\":\"drive-ide0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide0\"}' \\\n  -device 'ide-cd,bus=ide.0,unit=0,drive=drive-ide0,id=ide0,bootindex=200' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/one.iso\",\"node-name\":\"e247a6535f864815c8e9dbb5a118336\",\"read-only\":true},\"node-name\":\"f247a6535f864815c8e9dbb5a118336\",\"read-only\":true},\"node-name\":\"drive-ide1\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide1\"}' \\\n  -device 'ide-cd,bus=ide.0,unit=1,drive=drive-ide1,id=ide1,bootindex=201' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/two.iso\",\"node-name\":\"ec78a64f692c2fa9f873a111580aebd\",\"read-only\":true},\"node-name\":\"fc78a64f692c2fa9f873a111580aebd\",\"read-only\":true},\"node-name\":\"drive-ide2\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide2\"}' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=202' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/three.iso\",\"node-name\":\"e35557bae4bcbf9edc9f7ff7f132f30\",\"read-only\":true},\"node-name\":\"f35557bae4bcbf9edc9f7ff7f132f30\",\"read-only\":true},\"node-name\":\"drive-ide3\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide3\"}' \\\n  -device 'ide-cd,bus=ide.1,unit=1,drive=drive-ide3,id=ide3,bootindex=203' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-2.qcow2\",\"node-name\":\"ec11e0572184321efc5835152b95d5d\",\"read-only\":false},\"node-name\":\"fc11e0572184321efc5835152b95d5d\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hotplug-hugepages.conf",
    "content": "# TEST: memory hotplug with 1GB hugepage\ncores: 2\nmemory: 18432\nname: simple\nnuma: 1\nostype: l26\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 2\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nhotplug: memory\nhugepages: 1024\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hotplug-hugepages.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '4,sockets=2,cores=2,maxcpus=4' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 'size=2048,slots=255,maxmem=524288M' \\\n  -object 'memory-backend-file,id=ram-node0,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -numa 'node,nodeid=0,cpus=0-1,memdev=ram-node0' \\\n  -object 'memory-backend-file,id=ram-node1,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -numa 'node,nodeid=1,cpus=2-3,memdev=ram-node1' \\\n  -object 'memory-backend-file,id=mem-dimm0,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm0,memdev=mem-dimm0,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm1,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm1,memdev=mem-dimm1,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm2,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm2,memdev=mem-dimm2,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm3,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm3,memdev=mem-dimm3,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm4,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm4,memdev=mem-dimm4,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm5,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm5,memdev=mem-dimm5,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm6,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm6,memdev=mem-dimm6,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm7,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm7,memdev=mem-dimm7,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm8,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm8,memdev=mem-dimm8,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm9,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm9,memdev=mem-dimm9,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm10,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm10,memdev=mem-dimm10,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm11,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm11,memdev=mem-dimm11,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm12,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm12,memdev=mem-dimm12,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm13,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm13,memdev=mem-dimm13,node=1' \\\n  -object 'memory-backend-file,id=mem-dimm14,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm14,memdev=mem-dimm14,node=0' \\\n  -object 'memory-backend-file,id=mem-dimm15,size=1024M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -device 'pc-dimm,id=dimm15,memdev=mem-dimm15,node=1' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hotplug.conf",
    "content": "# TEST: basic memory hotplug\ncores: 2\nmemory: 66560\nname: simple\nnuma: 1\nostype: l26\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 2\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nhotplug: memory\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hotplug.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '4,sockets=2,cores=2,maxcpus=4' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 'size=1024,slots=255,maxmem=524288M' \\\n  -object 'memory-backend-ram,id=ram-node0,size=512M' \\\n  -numa 'node,nodeid=0,cpus=0-1,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=512M' \\\n  -numa 'node,nodeid=1,cpus=2-3,memdev=ram-node1' \\\n  -object 'memory-backend-ram,id=mem-dimm0,size=512M' \\\n  -device 'pc-dimm,id=dimm0,memdev=mem-dimm0,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm1,size=512M' \\\n  -device 'pc-dimm,id=dimm1,memdev=mem-dimm1,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm2,size=512M' \\\n  -device 'pc-dimm,id=dimm2,memdev=mem-dimm2,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm3,size=512M' \\\n  -device 'pc-dimm,id=dimm3,memdev=mem-dimm3,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm4,size=512M' \\\n  -device 'pc-dimm,id=dimm4,memdev=mem-dimm4,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm5,size=512M' \\\n  -device 'pc-dimm,id=dimm5,memdev=mem-dimm5,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm6,size=512M' \\\n  -device 'pc-dimm,id=dimm6,memdev=mem-dimm6,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm7,size=512M' \\\n  -device 'pc-dimm,id=dimm7,memdev=mem-dimm7,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm8,size=512M' \\\n  -device 'pc-dimm,id=dimm8,memdev=mem-dimm8,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm9,size=512M' \\\n  -device 'pc-dimm,id=dimm9,memdev=mem-dimm9,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm10,size=512M' \\\n  -device 'pc-dimm,id=dimm10,memdev=mem-dimm10,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm11,size=512M' \\\n  -device 'pc-dimm,id=dimm11,memdev=mem-dimm11,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm12,size=512M' \\\n  -device 'pc-dimm,id=dimm12,memdev=mem-dimm12,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm13,size=512M' \\\n  -device 'pc-dimm,id=dimm13,memdev=mem-dimm13,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm14,size=512M' \\\n  -device 'pc-dimm,id=dimm14,memdev=mem-dimm14,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm15,size=512M' \\\n  -device 'pc-dimm,id=dimm15,memdev=mem-dimm15,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm16,size=512M' \\\n  -device 'pc-dimm,id=dimm16,memdev=mem-dimm16,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm17,size=512M' \\\n  -device 'pc-dimm,id=dimm17,memdev=mem-dimm17,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm18,size=512M' \\\n  -device 'pc-dimm,id=dimm18,memdev=mem-dimm18,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm19,size=512M' \\\n  -device 'pc-dimm,id=dimm19,memdev=mem-dimm19,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm20,size=512M' \\\n  -device 'pc-dimm,id=dimm20,memdev=mem-dimm20,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm21,size=512M' \\\n  -device 'pc-dimm,id=dimm21,memdev=mem-dimm21,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm22,size=512M' \\\n  -device 'pc-dimm,id=dimm22,memdev=mem-dimm22,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm23,size=512M' \\\n  -device 'pc-dimm,id=dimm23,memdev=mem-dimm23,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm24,size=512M' \\\n  -device 'pc-dimm,id=dimm24,memdev=mem-dimm24,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm25,size=512M' \\\n  -device 'pc-dimm,id=dimm25,memdev=mem-dimm25,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm26,size=512M' \\\n  -device 'pc-dimm,id=dimm26,memdev=mem-dimm26,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm27,size=512M' \\\n  -device 'pc-dimm,id=dimm27,memdev=mem-dimm27,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm28,size=512M' \\\n  -device 'pc-dimm,id=dimm28,memdev=mem-dimm28,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm29,size=512M' \\\n  -device 'pc-dimm,id=dimm29,memdev=mem-dimm29,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm30,size=512M' \\\n  -device 'pc-dimm,id=dimm30,memdev=mem-dimm30,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm31,size=512M' \\\n  -device 'pc-dimm,id=dimm31,memdev=mem-dimm31,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm32,size=1024M' \\\n  -device 'pc-dimm,id=dimm32,memdev=mem-dimm32,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm33,size=1024M' \\\n  -device 'pc-dimm,id=dimm33,memdev=mem-dimm33,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm34,size=1024M' \\\n  -device 'pc-dimm,id=dimm34,memdev=mem-dimm34,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm35,size=1024M' \\\n  -device 'pc-dimm,id=dimm35,memdev=mem-dimm35,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm36,size=1024M' \\\n  -device 'pc-dimm,id=dimm36,memdev=mem-dimm36,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm37,size=1024M' \\\n  -device 'pc-dimm,id=dimm37,memdev=mem-dimm37,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm38,size=1024M' \\\n  -device 'pc-dimm,id=dimm38,memdev=mem-dimm38,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm39,size=1024M' \\\n  -device 'pc-dimm,id=dimm39,memdev=mem-dimm39,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm40,size=1024M' \\\n  -device 'pc-dimm,id=dimm40,memdev=mem-dimm40,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm41,size=1024M' \\\n  -device 'pc-dimm,id=dimm41,memdev=mem-dimm41,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm42,size=1024M' \\\n  -device 'pc-dimm,id=dimm42,memdev=mem-dimm42,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm43,size=1024M' \\\n  -device 'pc-dimm,id=dimm43,memdev=mem-dimm43,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm44,size=1024M' \\\n  -device 'pc-dimm,id=dimm44,memdev=mem-dimm44,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm45,size=1024M' \\\n  -device 'pc-dimm,id=dimm45,memdev=mem-dimm45,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm46,size=1024M' \\\n  -device 'pc-dimm,id=dimm46,memdev=mem-dimm46,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm47,size=1024M' \\\n  -device 'pc-dimm,id=dimm47,memdev=mem-dimm47,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm48,size=1024M' \\\n  -device 'pc-dimm,id=dimm48,memdev=mem-dimm48,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm49,size=1024M' \\\n  -device 'pc-dimm,id=dimm49,memdev=mem-dimm49,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm50,size=1024M' \\\n  -device 'pc-dimm,id=dimm50,memdev=mem-dimm50,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm51,size=1024M' \\\n  -device 'pc-dimm,id=dimm51,memdev=mem-dimm51,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm52,size=1024M' \\\n  -device 'pc-dimm,id=dimm52,memdev=mem-dimm52,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm53,size=1024M' \\\n  -device 'pc-dimm,id=dimm53,memdev=mem-dimm53,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm54,size=1024M' \\\n  -device 'pc-dimm,id=dimm54,memdev=mem-dimm54,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm55,size=1024M' \\\n  -device 'pc-dimm,id=dimm55,memdev=mem-dimm55,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm56,size=1024M' \\\n  -device 'pc-dimm,id=dimm56,memdev=mem-dimm56,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm57,size=1024M' \\\n  -device 'pc-dimm,id=dimm57,memdev=mem-dimm57,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm58,size=1024M' \\\n  -device 'pc-dimm,id=dimm58,memdev=mem-dimm58,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm59,size=1024M' \\\n  -device 'pc-dimm,id=dimm59,memdev=mem-dimm59,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm60,size=1024M' \\\n  -device 'pc-dimm,id=dimm60,memdev=mem-dimm60,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm61,size=1024M' \\\n  -device 'pc-dimm,id=dimm61,memdev=mem-dimm61,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm62,size=1024M' \\\n  -device 'pc-dimm,id=dimm62,memdev=mem-dimm62,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm63,size=1024M' \\\n  -device 'pc-dimm,id=dimm63,memdev=mem-dimm63,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm64,size=2048M' \\\n  -device 'pc-dimm,id=dimm64,memdev=mem-dimm64,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm65,size=2048M' \\\n  -device 'pc-dimm,id=dimm65,memdev=mem-dimm65,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm66,size=2048M' \\\n  -device 'pc-dimm,id=dimm66,memdev=mem-dimm66,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm67,size=2048M' \\\n  -device 'pc-dimm,id=dimm67,memdev=mem-dimm67,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm68,size=2048M' \\\n  -device 'pc-dimm,id=dimm68,memdev=mem-dimm68,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm69,size=2048M' \\\n  -device 'pc-dimm,id=dimm69,memdev=mem-dimm69,node=1' \\\n  -object 'memory-backend-ram,id=mem-dimm70,size=2048M' \\\n  -device 'pc-dimm,id=dimm70,memdev=mem-dimm70,node=0' \\\n  -object 'memory-backend-ram,id=mem-dimm71,size=2048M' \\\n  -device 'pc-dimm,id=dimm71,memdev=mem-dimm71,node=1' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hugepages-1g.conf",
    "content": "# TEST: memory with 1gb hugepages\ncores: 2\nmemory: 8192\nname: simple\nnuma: 1\nostype: l26\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 2\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nhugepages: 1024\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hugepages-1g.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '4,sockets=2,cores=2,maxcpus=4' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 8192 \\\n  -object 'memory-backend-file,id=ram-node0,size=4096M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -numa 'node,nodeid=0,cpus=0-1,memdev=ram-node0' \\\n  -object 'memory-backend-file,id=ram-node1,size=4096M,mem-path=/run/hugepages/kvm/1048576kB,share=on,prealloc=yes' \\\n  -numa 'node,nodeid=1,cpus=2-3,memdev=ram-node1' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hugepages-2m.conf",
    "content": "# TEST: memory with 2mb hugepages\ncores: 2\nmemory: 8192\nname: simple\nnuma: 1\nostype: l26\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 2\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nhugepages: 2\n"
  },
  {
    "path": "src/test/cfg2cmd/memory-hugepages-2m.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '4,sockets=2,cores=2,maxcpus=4' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 8192 \\\n  -object 'memory-backend-file,id=ram-node0,size=4096M,mem-path=/run/hugepages/kvm/2048kB,share=on,prealloc=yes' \\\n  -numa 'node,nodeid=0,cpus=0-1,memdev=ram-node0' \\\n  -object 'memory-backend-file,id=ram-node1,size=4096M,mem-path=/run/hugepages/kvm/2048kB,share=on,prealloc=yes' \\\n  -numa 'node,nodeid=1,cpus=2-3,memdev=ram-node1' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/minimal-defaults-to-new-machine.conf",
    "content": "# TEST: newer machine version than QEMU version installed on node\n# QEMU_VERSION: 9.0.0\n# EXPECT_ERROR: Installed QEMU version '9.0.0' is too old to run machine type 'pc-q35-42.9+pve0', please upgrade node 'localhost'\nsmbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3\nmachine: pc-q35-42.9\n"
  },
  {
    "path": "src/test/cfg2cmd/minimal-defaults-unsupported-pve-version.conf",
    "content": "# TEST: newer machine version than QEMU version installed on node\n# QEMU_VERSION: 9.0.0\n# EXPECT_ERROR: Installed qemu-server (max feature level for 9.0 is pve0) is too old to run machine type 'pc-q35-9.0+pve77', please upgrade node 'localhost'\nsmbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3\nmachine: pc-q35-9.0+pve77\n"
  },
  {
    "path": "src/test/cfg2cmd/minimal-defaults.conf",
    "content": "# TEST: minimal configuration to detect default changes\nsmbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3\n"
  },
  {
    "path": "src/test/cfg2cmd/minimal-defaults.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev-7.0-multiqueues.conf",
    "content": "# TEST: Simple test for netdev multi queue on 7.0 machine version\nbootdisk: scsi0\ncores: 3\nmachine: pc-i440fx-7.0\nmemory: 768\nname: netdev-multiq\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0,mtu=900,queues=2\nostype: l26\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev-7.0-multiqueues.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'netdev-multiq,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on,queues=2' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,vectors=6,mq=on,bootindex=300,host_mtu=900' \\\n  -machine 'type=pc-i440fx-7.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev-7.1-multiqueues.conf",
    "content": "# TEST: Simple test for netdev related stuff\nbootdisk: scsi0\ncores: 3\nmemory: 768\nname: netdev\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0,mtu=900,queues=2\nostype: l26\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev-7.1-multiqueues.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'netdev,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on,queues=2' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,vectors=6,mq=on,packed=on,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=900' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev-7.1.conf",
    "content": "# TEST: Simple test for netdev related stuff\nbootdisk: scsi0\ncores: 3\nmemory: 768\nname: netdev\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0,mtu=900\nostype: l26\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev-7.1.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'netdev,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=900' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev.conf",
    "content": "# TEST: Simple test for netdev related stuff\nbootdisk: scsi0\ncores: 3\nmachine: pc-i440fx-5.0\nmemory: 768\nname: netdev\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0,mtu=900\nostype: l26\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'netdev,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300,host_mtu=900' \\\n  -machine 'type=pc-i440fx-5.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev_vxlan.conf",
    "content": "# TEST: Test inheriting the MTU from a bridge with MTU != 1500\nbootdisk: scsi0\ncores: 3\nmemory: 768\nname: netdev\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vxlan_bridge\nostype: l26\n"
  },
  {
    "path": "src/test/cfg2cmd/netdev_vxlan.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'netdev,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1450' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/old-qemu.conf",
    "content": "# TEST: Test QEMU version detection and expect fail on old version\n# QEMU_VERSION: 5.0.1\n# EXPECT_ERROR: Detected old QEMU binary ('5.0.1', at least 6.0 is required)\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\n"
  },
  {
    "path": "src/test/cfg2cmd/os-l24.conf",
    "content": "# TEST: Simple test for Linux OS type l24\nostype: l24\nvga: qxl\n"
  },
  {
    "path": "src/test/cfg2cmd/os-l24.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'qxl-vga,id=vga,max_outputs=4,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/os-other.conf",
    "content": "# TEST: Simple test for OS type other\nostype: other\n"
  },
  {
    "path": "src/test/cfg2cmd/os-other.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/os-solaris.conf",
    "content": "# TEST: Simple test for OS type Solaris\nostype: solaris\n"
  },
  {
    "path": "src/test/cfg2cmd/os-solaris.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep,-x2apic \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/ostype-usb13-error.conf",
    "content": "# TEST: Test error for old ostype type with newer usb config\n# EXPECT_ERROR: using usb13 is only possible with machine type >= 7.1 and ostype l26 or windows > 7\ncores: 2\nmemory: 768\nname: q35-usb3-error\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: w2k\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb13: spice\n"
  },
  {
    "path": "src/test/cfg2cmd/pinned-version-pxe-pve.conf",
    "content": "# TEST: for a basic configuration with a .pxe machine and +pve pinned\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmachine: pc-q35-4.1+pve2.pxe\nmemory: 1024\nname: pinned\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.raw,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=c7fdd046-fefc-11e9-832e-770e1d5636a0\nsockets: 1\nvmgenid: bdd46b98-fefc-11e9-97b4-d72c378e0f96\n# add rng0 to stress +pve2 version requirement\nrng0: source=/dev/urandom\n"
  },
  {
    "path": "src/test/cfg2cmd/pinned-version-pxe-pve.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'pinned,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=c7fdd046-fefc-11e9-832e-770e1d5636a0' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 1024 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=bdd46b98-fefc-11e9-97b4-d72c378e0f96' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -object 'rng-random,filename=/dev/urandom,id=rng0' \\\n  -device 'virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000,bus=pci.1,addr=0x1d' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -drive 'if=none,id=drive-ide2,media=cdrom,aio=io_uring' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -drive 'file=/var/lib/vz/images/8006/vm-8006-disk-0.raw,if=none,id=drive-scsi0,discard=on,format=raw,cache=none,aio=io_uring,detect-zeroes=unmap' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300,romfile=pxe-virtio.rom' \\\n  -machine 'type=pc-q35-4.1+pve2'\n"
  },
  {
    "path": "src/test/cfg2cmd/pinned-version-pxe.conf",
    "content": "# TEST: for a basic configuration with a .pxe machine\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmachine: pc-q35-5.1.pxe\nmemory: 1024\nname: pinned\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.raw,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=c7fdd046-fefc-11e9-832e-770e1d5636a0\nsockets: 1\nvmgenid: bdd46b98-fefc-11e9-97b4-d72c378e0f96\n"
  },
  {
    "path": "src/test/cfg2cmd/pinned-version-pxe.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'pinned,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=c7fdd046-fefc-11e9-832e-770e1d5636a0' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 1024 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=bdd46b98-fefc-11e9-97b4-d72c378e0f96' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -drive 'if=none,id=drive-ide2,media=cdrom,aio=io_uring' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -drive 'file=/var/lib/vz/images/8006/vm-8006-disk-0.raw,if=none,id=drive-scsi0,discard=on,format=raw,cache=none,aio=io_uring,detect-zeroes=unmap' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300,romfile=pxe-virtio.rom' \\\n  -machine 'type=pc-q35-5.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/pinned-version.conf",
    "content": "# TEST: Simple test for a basic configuration with pinned QEMU machine version\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmachine: pc-q35-3.1\nmemory: 1024\nname: pinned\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.raw,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=c7fdd046-fefc-11e9-832e-770e1d5636a0\nsockets: 1\nvmgenid: bdd46b98-fefc-11e9-97b4-d72c378e0f96\n"
  },
  {
    "path": "src/test/cfg2cmd/pinned-version.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'pinned,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=c7fdd046-fefc-11e9-832e-770e1d5636a0' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 1024 \\\n  -readconfig /usr/share/qemu-server/pve-q35.cfg \\\n  -device 'vmgenid,guid=bdd46b98-fefc-11e9-97b4-d72c378e0f96' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -drive 'if=none,id=drive-ide2,media=cdrom,aio=io_uring' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -drive 'file=/var/lib/vz/images/8006/vm-8006-disk-0.raw,if=none,id=drive-scsi0,discard=on,format=raw,cache=none,aio=io_uring,detect-zeroes=unmap' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-q35-3.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-ide.conf",
    "content": "# TEST: Config with q35, Linux & four IDE CD-ROMs\nbootdisk: scsi0\ncores: 2\nide0: cifs-store:iso/zero.iso,media=cdrom,size=112M\nide1: cifs-store:iso/one.iso,media=cdrom,size=112M\nide2: cifs-store:iso/two.iso,media=cdrom,size=112M\nide3: cifs-store:iso/three.iso,media=cdrom,size=112M\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsi0: local:100/vm-100-disk-2.qcow2,size=10G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-ide.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-ide0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-ide1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-ide2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-ide3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/zero.iso\",\"node-name\":\"e1677eafc00b7016099210662868e38\",\"read-only\":true},\"node-name\":\"f1677eafc00b7016099210662868e38\",\"read-only\":true},\"node-name\":\"drive-ide0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide0\"}' \\\n  -device 'ide-cd,bus=ide.0,unit=0,drive=drive-ide0,id=ide0,bootindex=200' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/one.iso\",\"node-name\":\"e247a6535f864815c8e9dbb5a118336\",\"read-only\":true},\"node-name\":\"f247a6535f864815c8e9dbb5a118336\",\"read-only\":true},\"node-name\":\"drive-ide1\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide1\"}' \\\n  -device 'ide-cd,bus=ide.2,unit=0,drive=drive-ide1,id=ide1,bootindex=201' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/two.iso\",\"node-name\":\"ec78a64f692c2fa9f873a111580aebd\",\"read-only\":true},\"node-name\":\"fc78a64f692c2fa9f873a111580aebd\",\"read-only\":true},\"node-name\":\"drive-ide2\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide2\"}' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=202' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/template/iso/three.iso\",\"node-name\":\"e35557bae4bcbf9edc9f7ff7f132f30\",\"read-only\":true},\"node-name\":\"f35557bae4bcbf9edc9f7ff7f132f30\",\"read-only\":true},\"node-name\":\"drive-ide3\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide3\"}' \\\n  -device 'ide-cd,bus=ide.3,unit=0,drive=drive-ide3,id=ide3,bootindex=203' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-2.qcow2\",\"node-name\":\"ec11e0572184321efc5835152b95d5d\",\"read-only\":false},\"node-name\":\"fc11e0572184321efc5835152b95d5d\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-driver-keep.conf",
    "content": "# TEST: Config with q35, NUMA, hostpci passthrough, EFI & Linux & driver option\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: 00:ff.1,driver=keep\nhostpci1: d0:13.0,pcie=1,driver=vfio\nhostpci2: 00:f4.0\nhostpci3: d0:15.1,pcie=1\nhostpci4: d0:17.0,pcie=1,rombar=0\nhostpci7: d0:15.2,pcie=1\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-driver-keep.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-1.qcow2\",\"node-name\":\"e70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"f70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'vfio-pci,host=0000:00:ff.1,id=hostpci0,bus=pci.0,addr=0x10' \\\n  -device 'vfio-pci,host=0000:d0:13.0,id=hostpci1,bus=ich9-pcie-port-2,addr=0x0' \\\n  -device 'vfio-pci,host=0000:00:f4.0,id=hostpci2,bus=pci.0,addr=0x1b' \\\n  -device 'vfio-pci,host=0000:d0:15.1,id=hostpci3,bus=ich9-pcie-port-4,addr=0x0' \\\n  -device 'pcie-root-port,id=ich9-pcie-port-5,addr=10.0,x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port=5,chassis=5' \\\n  -device 'vfio-pci,host=0000:d0:17.0,id=hostpci4,bus=ich9-pcie-port-5,addr=0x0,rombar=0' \\\n  -device 'pcie-root-port,id=ich9-pcie-port-8,addr=10.3,x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port=8,chassis=8' \\\n  -device 'vfio-pci,host=0000:d0:15.2,id=hostpci7,bus=ich9-pcie-port-8,addr=0x0' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-mapping.conf",
    "content": "# TEST: Config with q35, NUMA, hostpci mapping passthrough, EFI & Linux\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: mapping=someNic\nhostpci1: mapping=someGpu,mdev=some-model\nhostpci2: mapping=someNic\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-mapping.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-1.qcow2\",\"node-name\":\"e70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"f70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'vfio-pci,host=0000:07:10.0,id=hostpci0,bus=pci.0,addr=0x10' \\\n  -device 'vfio-pci,sysfsdev=/sys/bus/mdev/devices/00000001-0000-0000-0000-000000008006,id=hostpci1,bus=pci.0,addr=0x11' \\\n  -device 'vfio-pci,host=0000:07:10.1,id=hostpci2,bus=pci.0,addr=0x1b' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-multifunction.conf",
    "content": "# TEST: Config with q35, NUMA, hostpci passthrough, EFI & Linux\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: f0:43\nhostpci1: 1234:f0:43\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-multifunction.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-1.qcow2\",\"node-name\":\"e70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"f70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'vfio-pci,host=0000:f0:43.0,id=hostpci0.0,bus=pci.0,addr=0x10.0,multifunction=on' \\\n  -device 'vfio-pci,host=0000:f0:43.1,id=hostpci0.1,bus=pci.0,addr=0x10.1' \\\n  -device 'vfio-pci,host=1234:f0:43.1,id=hostpci1,bus=pci.0,addr=0x11' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-template.conf",
    "content": "# TEST: Config with q35, NUMA, hostpci passthrough, EFI, TPM & Linux as a template\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/base-100-disk-1.qcow2,size=128K\nhostpci0: 00:ff.1\nhostpci1: d0:13.0,pcie=1\nhostpci2: 00:f4.0\nhostpci3: d0:15.1,pcie=1\nhostpci4: d0:17.0,pcie=1,rombar=0\nhostpci7: d0:15.2,pcie=1\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nscsihw: virtio-scsi-pci\nscsi0: local:100/base-100-disk-2.raw,size=10G\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\ntemplate: 1\ntpmstate0: local:100/base-100-disk-1.raw,size=4M,version=v2.0\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-template.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/base-100-disk-1.qcow2\",\"node-name\":\"eb6bec0e3c391fabb7fb7dd73ced9bf\",\"read-only\":true},\"node-name\":\"fb6bec0e3c391fabb7fb7dd73ced9bf\",\"read-only\":true},\"node-name\":\"drive-efidisk0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vga none \\\n  -nographic \\\n  -cpu qemu64 \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/base-100-disk-2.raw\",\"node-name\":\"e24dfe239201bb9924fc4cfb899ca70\",\"read-only\":true},\"node-name\":\"f24dfe239201bb9924fc4cfb899ca70\",\"read-only\":true},\"node-name\":\"drive-scsi0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,write-cache=on' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,accel=tcg,type=pc+pve0' \\\n  -snapshot\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-x-pci-overrides.conf",
    "content": "# TEST: Overriding PCI vendor/device IDs reported to guest\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: 00:ff.1,vendor-id=0x1234,device-id=0x5678,sub-vendor-id=0x2233,sub-device-id=0x0000\nhostpci1: d0:13.0,pcie=1,vendor-id=0x1234,device-id=0x5678\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci-x-pci-overrides.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-1.qcow2\",\"node-name\":\"e70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"f70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'vfio-pci,host=0000:00:ff.1,id=hostpci0,bus=pci.0,addr=0x10,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678,x-pci-sub-vendor-id=0x2233,x-pci-sub-device-id=0x0000' \\\n  -device 'vfio-pci,host=0000:d0:13.0,id=hostpci1,bus=ich9-pcie-port-2,addr=0x0,x-pci-vendor-id=0x1234,x-pci-device-id=0x5678' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci.conf",
    "content": "# TEST: Config with q35, NUMA, hostpci passthrough, EFI & Linux\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: 00:ff.1\nhostpci1: d0:13.0,pcie=1\nhostpci2: 00:f4.0\nhostpci3: d0:15.1,pcie=1\nhostpci4: d0:17.0,pcie=1,rombar=0\nhostpci7: d0:15.2,pcie=1\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-linux-hostpci.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-1.qcow2\",\"node-name\":\"e70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"f70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'vfio-pci,host=0000:00:ff.1,id=hostpci0,bus=pci.0,addr=0x10' \\\n  -device 'vfio-pci,host=0000:d0:13.0,id=hostpci1,bus=ich9-pcie-port-2,addr=0x0' \\\n  -device 'vfio-pci,host=0000:00:f4.0,id=hostpci2,bus=pci.0,addr=0x1b' \\\n  -device 'vfio-pci,host=0000:d0:15.1,id=hostpci3,bus=ich9-pcie-port-4,addr=0x0' \\\n  -device 'pcie-root-port,id=ich9-pcie-port-5,addr=10.0,x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port=5,chassis=5' \\\n  -device 'vfio-pci,host=0000:d0:17.0,id=hostpci4,bus=ich9-pcie-port-5,addr=0x0,rombar=0' \\\n  -device 'pcie-root-port,id=ich9-pcie-port-8,addr=10.3,x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port=8,chassis=8' \\\n  -device 'vfio-pci,host=0000:d0:15.2,id=hostpci7,bus=ich9-pcie-port-8,addr=0x0' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-no-viommu-with-aw-bits.conf",
    "content": "# TEST: Check that aw-bits cannot be set without viommu\n# EXPECT_ERROR: cannot set aw-bits if no vIOMMU is configured\nmachine: q35,aw-bits=39\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple-6.0.conf",
    "content": "# TEST: Config with q35, Linux & nothing much else but on 6.0\nbios: ovmf\nbootdisk: scsi0\ncores: 2\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nmachine: pc-q35-6.0\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple-6.0.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \\\n  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-q35-6.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple-7.0.conf",
    "content": "# TEST: Config with q35, Linux & nothing much else but on 7.0\nbios: ovmf\nbootdisk: scsi0\ncores: 2\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nmachine: pc-q35-7.0\nmeta: creation-qemu=6.1\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple-7.0.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \\\n  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-q35-7.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple-pinned-6.1.conf",
    "content": "# TEST: Config with q35, Linux & nothing much else\n#\nbios: ovmf\nbootdisk: scsi0\ncores: 2\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nmachine: pc-q35-6.1\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple-pinned-6.1.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \\\n  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-q35-6.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple.conf",
    "content": "# TEST: Config with q35, Linux & nothing much else\n#\nbios: ovmf\nbootdisk: scsi0\ncores: 2\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-simple.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_CODE.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-1.qcow2\",\"node-name\":\"e70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"f70e3017c5a79fdee5a04aa92ac1e9c\",\"read-only\":false},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,hpet=off,type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-usb13-error.conf",
    "content": "# TEST: Test usb error for q35 and older machine type\n# EXPECT_ERROR: using usb13 is only possible with machine type >= 7.1 and ostype l26 or windows > 7\ncores: 2\nmemory: 768\nname: q35-usb3-error\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nmachine: pc-q35-4.0\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb13: spice\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-usb2.conf",
    "content": "# TEST: Test Q35 USB2 passthrough combination\ncores: 2\nmemory: 768\nname: q35-usb2\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nmachine: pc-q35-4.0\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb1: spice\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-usb2.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'q35-usb2,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0' \\\n  -device 'qxl-vga,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-q35-4.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-usb3.conf",
    "content": "# TEST: Test Q35 USB3 passthrough combination\ncores: 2\nmemory: 768\nname: q35-usb3\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nmachine: pc-q35-4.0\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb1: spice,usb3=1\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-usb3.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'q35-usb3,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'nec-usb-xhci,id=xhci,bus=pci.1,addr=0x1b' \\\n  -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0' \\\n  -device 'qxl-vga,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-q35-4.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-intel-aw-bits.conf",
    "content": "# TEST: Check if aw-bits are propagated correctly to intel-iommu device\nmachine: q35,viommu=intel,aw-bits=39\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-intel-aw-bits.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -device 'intel-iommu,intremap=on,caching-mode=on,aw-bits=39' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=q35+pve0,kernel-irqchip=split'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-intel-guest-phys-bits.conf",
    "content": "machine: q35,viommu=intel\ncpu: host,guest-phys-bits=39\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-intel-guest-phys-bits.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu 'host,+kvm_pv_eoi,+kvm_pv_unhalt,guest-phys-bits=39' \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -device 'intel-iommu,intremap=on,caching-mode=on' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=q35+pve0,kernel-irqchip=split'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-intel.conf",
    "content": "machine: q35,viommu=intel\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-intel.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -device 'intel-iommu,intremap=on,caching-mode=on' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=q35+pve0,kernel-irqchip=split'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-virtio-aw-bits.conf",
    "content": "# TEST: Check if aw-bits are propagated correctly to virtio-iommu-pci device\nmachine: q35,viommu=virtio,aw-bits=39\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-virtio-aw-bits.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'virtio-iommu-pci,aw-bits=39' \\\n  -machine 'type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-virtio.conf",
    "content": "machine: type=q35,viommu=virtio\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-viommu-virtio.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'ICH9-LPC.disable_s3=1' \\\n  -global 'ICH9-LPC.disable_s4=1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device virtio-iommu-pci \\\n  -machine 'type=q35+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-win10-hostpci.conf",
    "content": "# TEST: Config with q35, NUMA, hostpci passthrough, EFI & Windows\nbios: ovmf\nbootdisk: scsi0\ncores: 1\nefidisk0: local:100/vm-100-disk-1.qcow2,size=128K\nhostpci0: f0:42.0\nhostpci1: f0:43.0,pcie=1\nhostpci4: 00:43.1,pcie=1\nmachine: q35\nmemory: 512\nnet0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0\nnuma: 1\nostype: win10\nscsihw: virtio-scsi-pci\nsmbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687\nsockets: 2\nvmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-win10-hostpci.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \\\n  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \\\n  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \\\n  -smp '2,sockets=2,cores=1,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \\\n  -m 512 \\\n  -object 'memory-backend-ram,id=ram-node0,size=256M' \\\n  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \\\n  -object 'memory-backend-ram,id=ram-node1,size=256M' \\\n  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'vfio-pci,host=0000:f0:42.0,id=hostpci0,bus=pci.0,addr=0x10' \\\n  -device 'vfio-pci,host=0000:f0:43.0,id=hostpci1,bus=ich9-pcie-port-2,addr=0x0' \\\n  -device 'pcie-root-port,id=ich9-pcie-port-5,addr=10.0,x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port=5,chassis=5' \\\n  -device 'vfio-pci,host=0000:00:43.1,id=hostpci4,bus=ich9-pcie-port-5,addr=0x0' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -rtc 'driftfix=slew,base=localtime' \\\n  -machine 'hpet=off,type=pc-q35-5.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-windows-pinning.conf",
    "content": "# TEST: Config with q35, win, meta to test version pinning\n#\nmachine: q35\nmeta: creation-qemu=9.2.0,ctime=1741179133\nostype: win11\n"
  },
  {
    "path": "src/test/cfg2cmd/q35-windows-pinning.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \\\n  -m 512 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1,edid=off' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -rtc 'driftfix=slew,base=localtime' \\\n  -machine 'hpet=off,type=pc-q35-9.2+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/qemu-xhci-7.1.conf",
    "content": "# TEST: Test for new xhci controller with new machine version\ncores: 2\nmachine: pc-i440fx-7.1\nmemory: 768\nname: spiceusb3\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb1: spice\nusb5: spice\nusb13: host=1-14\n"
  },
  {
    "path": "src/test/cfg2cmd/qemu-xhci-7.1.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'spiceusb3,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'qemu-xhci,p2=15,p3=15,id=xhci,bus=pci.1,addr=0x1b' \\\n  -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0,port=2' \\\n  -chardev 'spicevmc,id=usbredirchardev5,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev5,id=usbredirdev5,bus=xhci.0,port=6' \\\n  -device 'usb-host,bus=xhci.0,port=14,hostbus=1,hostport=14,id=usb13' \\\n  -device 'qxl-vga,id=vga,max_outputs=4,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300' \\\n  -machine 'type=pc-i440fx-7.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/qemu-xhci-q35-7.1.conf",
    "content": "# TEST: Test Q35 USB passthrough combination with qemu-xhci\ncores: 2\nmemory: 768\nname: q35-qemu-xhci\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nmachine: pc-q35-7.1\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nusb1: spice\n"
  },
  {
    "path": "src/test/cfg2cmd/qemu-xhci-q35-7.1.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'q35-qemu-xhci,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'qemu-xhci,p2=15,p3=15,id=xhci,bus=pci.1,addr=0x1b' \\\n  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \\\n  -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0,port=2' \\\n  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300' \\\n  -machine 'type=pc-q35-7.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/qga-fs-freeze-backup-legacy.conf",
    "content": "# TEST: Ensure the deprecated freeze-fs-on-backup agent key is still parsed.\nagent: enabled=1,freeze-fs-on-backup=0\n"
  },
  {
    "path": "src/test/cfg2cmd/qga-fs-freeze-backup-legacy.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -chardev 'socket,path=/var/run/qemu-server/8006.qga,server=on,wait=off,id=qga0' \\\n  -device 'virtio-serial,id=qga0,bus=pci.0,addr=0x8' \\\n  -device 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/qga-fs-freeze.conf",
    "content": "# TEST: Ensure agent sub-properties do not affect the QEMU command line.\nagent: 1,freeze-fs=1\n"
  },
  {
    "path": "src/test/cfg2cmd/qga-fs-freeze.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -chardev 'socket,path=/var/run/qemu-server/8006.qga,server=on,wait=off,id=qga0' \\\n  -device 'virtio-serial,id=qga0,bus=pci.0,addr=0x8' \\\n  -device 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/qga-minimal.conf",
    "content": "# TEST: Ensure agent sub-properties do not affect the QEMU command line.\nagent: 1\n"
  },
  {
    "path": "src/test/cfg2cmd/qga-minimal.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -chardev 'socket,path=/var/run/qemu-server/8006.qga,server=on,wait=off,id=qga0' \\\n  -device 'virtio-serial,id=qga0,bus=pci.0,addr=0x8' \\\n  -device 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/scsiblk.conf",
    "content": "boot: order=scsi0\nmeta: creation-qemu=10.0.2,ctime=1755189639\nscsi0: /dev/sdg,scsiblock=1,size=32G\nsmbios1: uuid=d47e2d5c-7068-46d4-9733-ff94083e28f6\nvmgenid: 0a212e2f-0d9a-4006-971e-17be9fcf3efe\n"
  },
  {
    "path": "src/test/cfg2cmd/scsiblk.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=d47e2d5c-7068-46d4-9733-ff94083e28f6' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=0a212e2f-0d9a-4006-971e-17be9fcf3efe' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"host_device\",\"filename\":\"/dev/sdg\",\"node-name\":\"e8c91179e47ae3eb292a03618be5cf4\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-lsi.conf",
    "content": "# TEST: Simple test for LSI SCSI controller\nbootdisk: scsi0\nname: simple\nscsi0: lvm-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: lvm-store:vm-8006-disk-1,cache=writeback,discard=on,size=104858K\nscsi5: lvm-store:vm-8006-disk-2,cache=writethrough,discard=on,size=104858K\nscsi9: lvm-store:vm-8006-disk-3,cache=directsync,discard=on,size=104858K\nscsi16: lvm-store:vm-8006-disk-4,cache=directsync,discard=on,size=104858K\nscsihw: lsi\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-lsi.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi9\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi16\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'pci-bridge,id=pci.4,chassis_nr=4,bus=pci.1,addr=0x1c' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"f0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-1\",\"node-name\":\"e68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"f68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-2\",\"node-name\":\"e02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"f02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=off' \\\n  -device 'lsi,id=scsihw1,bus=pci.0,addr=0x6' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-3\",\"node-name\":\"e77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"f77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"drive-scsi9\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi9\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=2,drive=drive-scsi9,id=scsi9,device_id=drive-scsi9,write-cache=off' \\\n  -device 'lsi,id=scsihw2,bus=pci.4,addr=0x1' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-4\",\"node-name\":\"e86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"f86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"drive-scsi16\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi16\"}' \\\n  -device 'scsi-hd,bus=scsihw2.0,scsi-id=2,drive=drive-scsi16,id=scsi16,device_id=drive-scsi16,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-lsi53c810.conf",
    "content": "# TEST: Simple test for LSI 53c810 SCSI controller\nbootdisk: scsi0\nname: simple\nscsi0: lvm-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: lvm-store:vm-8006-disk-1,cache=writeback,discard=on,size=104858K\nscsi5: lvm-store:vm-8006-disk-2,cache=writethrough,discard=on,size=104858K\nscsi9: lvm-store:vm-8006-disk-3,cache=directsync,discard=on,size=104858K\nscsi16: lvm-store:vm-8006-disk-4,cache=directsync,discard=on,size=104858K\nscsihw: lsi53c810\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-lsi53c810.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi9\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi16\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'pci-bridge,id=pci.4,chassis_nr=4,bus=pci.1,addr=0x1c' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'lsi53c810,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"f0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-1\",\"node-name\":\"e68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"f68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-2\",\"node-name\":\"e02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"f02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=off' \\\n  -device 'lsi53c810,id=scsihw1,bus=pci.0,addr=0x6' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-3\",\"node-name\":\"e77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"f77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"drive-scsi9\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi9\"}' \\\n  -device 'scsi-hd,bus=scsihw1.0,scsi-id=2,drive=drive-scsi9,id=scsi9,device_id=drive-scsi9,write-cache=off' \\\n  -device 'lsi53c810,id=scsihw2,bus=pci.4,addr=0x1' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-4\",\"node-name\":\"e86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"f86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"drive-scsi16\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi16\"}' \\\n  -device 'scsi-hd,bus=scsihw2.0,scsi-id=2,drive=drive-scsi16,id=scsi16,device_id=drive-scsi16,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-megasas.conf",
    "content": "# TEST: Simple test for MegaRAID SAS SCSI controller\nbootdisk: scsi0\nname: simple\nscsi0: lvm-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: lvm-store:vm-8006-disk-1,cache=writeback,discard=on,size=104858K\nscsi5: lvm-store:vm-8006-disk-2,cache=writethrough,discard=on,size=104858K\nscsi9: lvm-store:vm-8006-disk-3,cache=directsync,discard=on,size=104858K\nscsi16: lvm-store:vm-8006-disk-4,cache=directsync,discard=on,size=104858K\nscsihw: megasas\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-megasas.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi9\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi16\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'megasas,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"f0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-1\",\"node-name\":\"e68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"f68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-2\",\"node-name\":\"e02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"f02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-3\",\"node-name\":\"e77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"f77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"drive-scsi9\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi9\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=9,drive=drive-scsi9,id=scsi9,device_id=drive-scsi9,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-4\",\"node-name\":\"e86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"f86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"drive-scsi16\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi16\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=16,drive=drive-scsi16,id=scsi16,device_id=drive-scsi16,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-pvscsi.conf",
    "content": "# TEST: Simple test for PVSCSI controller\nbootdisk: scsi0\nname: simple\nscsi0: lvm-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: lvm-store:vm-8006-disk-1,cache=writeback,discard=on,size=104858K\nscsi5: lvm-store:vm-8006-disk-2,cache=writethrough,discard=on,size=104858K\nscsi9: lvm-store:vm-8006-disk-3,cache=directsync,discard=on,size=104858K\nscsi16: lvm-store:vm-8006-disk-4,cache=directsync,discard=on,size=104858K\nscsihw: pvscsi\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-pvscsi.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi9\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi16\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'pvscsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"f0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-1\",\"node-name\":\"e68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"f68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-2\",\"node-name\":\"e02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"f02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-3\",\"node-name\":\"e77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"f77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"drive-scsi9\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi9\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=9,drive=drive-scsi9,id=scsi9,device_id=drive-scsi9,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-4\",\"node-name\":\"e86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"f86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"drive-scsi16\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi16\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=16,drive=drive-scsi16,id=scsi16,device_id=drive-scsi16,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-virtio-scsi-single.conf",
    "content": "# TEST: Simple test for VirtIO SCSI single controller\nbootdisk: scsi0\nname: simple\nscsi0: lvm-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: lvm-store:vm-8006-disk-1,cache=writeback,discard=on,size=104858K\nscsi5: lvm-store:vm-8006-disk-2,cache=writethrough,discard=on,size=104858K\nscsi9: lvm-store:vm-8006-disk-3,cache=directsync,discard=on,size=104858K\nscsi16: lvm-store:vm-8006-disk-4,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-single\n"
  },
  {
    "path": "src/test/cfg2cmd/scsihw-virtio-scsi-single.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi9\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi16\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'pci-bridge,id=pci.3,chassis_nr=3,bus=pci.0,addr=0x5' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'virtio-scsi-pci,id=virtioscsi0,bus=pci.3,addr=0x1' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"f0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=virtioscsi0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -device 'virtio-scsi-pci,id=virtioscsi1,bus=pci.3,addr=0x2' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-1\",\"node-name\":\"e68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"f68a63e6c55e4a6fe8c847a8f60af7e\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=virtioscsi1.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -device 'virtio-scsi-pci,id=virtioscsi5,bus=pci.3,addr=0x6' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-2\",\"node-name\":\"e02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"f02ef0cb3873a2ecfddb04f546b6b26\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=virtioscsi5.0,channel=0,scsi-id=0,lun=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=off' \\\n  -device 'virtio-scsi-pci,id=virtioscsi9,bus=pci.3,addr=0xa' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-3\",\"node-name\":\"e77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"f77e7a9ba0adc041419f45894ffb1ed\",\"read-only\":false},\"node-name\":\"drive-scsi9\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi9\"}' \\\n  -device 'scsi-hd,bus=virtioscsi9.0,channel=0,scsi-id=0,lun=9,drive=drive-scsi9,id=scsi9,device_id=drive-scsi9,write-cache=off' \\\n  -device 'virtio-scsi-pci,id=virtioscsi16,bus=pci.3,addr=0x11' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-4\",\"node-name\":\"e86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"f86ac0b43c3f3cb5be16052b349a9b7\",\"read-only\":false},\"node-name\":\"drive-scsi16\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi16\"}' \\\n  -device 'scsi-hd,bus=virtioscsi16.0,channel=0,scsi-id=0,lun=16,drive=drive-scsi16,id=scsi16,device_id=drive-scsi16,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/seabios_serial.conf",
    "content": "# TEST: Test for smm-related regression with SeaBIOS and serial display\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: seabiosserial\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nserial0: socket\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvga: serial0\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/seabios_serial.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'seabiosserial,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -nographic \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -chardev 'socket,id=serial0,path=/var/run/qemu-server/8006.serial0,server=on,wait=off' \\\n  -device 'isa-serial,chardev=serial0' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"ecd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"fcd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,smm=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/sev-es.conf",
    "content": "# TEST: Test raw efidisk size parameter\n# HW_CAPABILITIES: amd-turin-9005\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nefidisk0: local:100/vm-100-disk-0.raw,efitype=4m,pre-enrolled-keys=1,size=528K\namd-sev: type=es\n"
  },
  {
    "path": "src/test/cfg2cmd/sev-es.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_SEV_CODE_4M.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-0.raw\",\"node-name\":\"e1175f2a490414e7c53337589fde17a\",\"read-only\":false},\"node-name\":\"f1175f2a490414e7c53337589fde17a\",\"read-only\":false,\"size\":540672},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -object 'sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=6,policy=0xc' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0,confidential-guest-support=sev0'\n"
  },
  {
    "path": "src/test/cfg2cmd/sev-snp.conf",
    "content": "# TEST: Test raw efidisk size parameter\n# HW_CAPABILITIES: amd-turin-9005\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\namd-sev: type=snp\n"
  },
  {
    "path": "src/test/cfg2cmd/sev-snp.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -bios /usr/share/pve-edk2-firmware//OVMF_SEV_4M.fd \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -object 'sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=6,policy=0xb0000' \\\n  -machine 'type=pc+pve0,confidential-guest-support=sev0'\n"
  },
  {
    "path": "src/test/cfg2cmd/sev-std.conf",
    "content": "# TEST: Test raw efidisk size parameter\n# HW_CAPABILITIES: amd-turin-9005\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nbios: ovmf\nefidisk0: local:100/vm-100-disk-0.raw,efitype=4m,pre-enrolled-keys=1,size=528K\namd-sev: type=std\n"
  },
  {
    "path": "src/test/cfg2cmd/sev-std.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -object '{\"id\":\"throttle-drive-efidisk0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -blockdev '{\"driver\":\"raw\",\"file\":{\"driver\":\"file\",\"filename\":\"/usr/share/pve-edk2-firmware//OVMF_SEV_CODE_4M.fd\"},\"node-name\":\"pflash0\",\"read-only\":true}' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/100/vm-100-disk-0.raw\",\"node-name\":\"e1175f2a490414e7c53337589fde17a\",\"read-only\":false},\"node-name\":\"f1175f2a490414e7c53337589fde17a\",\"read-only\":false,\"size\":540672},\"node-name\":\"drive-efidisk0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-efidisk0\"}' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -object 'sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=6,policy=0x8' \\\n  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0,confidential-guest-support=sev0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-backingchain.conf",
    "content": "# TEST: Simple test for external snapshot backing chain\nname: simple\nparent: snap3\nscsi0: localsnapext:8006/vm-8006-disk-0.qcow2,size=1G\nscsi1: lvm-store:vm-8006-disk-0.qcow2,size=1G\n\n[snap1]\nname: simple\nscsi0: localsnapext:8006/vm-8006-disk-0.qcow2,size=1G\nscsi1: lvm-store:vm-8006-disk-0.qcow2,size=1G\nsnaptime: 1748933042\n\n[snap2]\nparent: snap1\nname: simple\nscsi0: localsnapext:8006/vm-8006-disk-0.qcow2,size=1G\nscsi1: lvm-store:vm-8006-disk-0.qcow2,size=1G\nsnaptime: 1748933043\n\n[snap3]\nparent: snap2\nname: simple\nscsi0: localsnapext:8006/vm-8006-disk-0.qcow2,size=1G\nscsi1: lvm-store:vm-8006-disk-0.qcow2,size=1G\nsnaptime: 1748933044\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-backingchain.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"backing\":{\"backing\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"discard-no-unref\":true,\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vzsnapext/images/8006/snap1-vm-8006-disk-0.qcow2\",\"node-name\":\"ea91a385a49a008a4735c0aec5c6749\",\"read-only\":false},\"node-name\":\"fa91a385a49a008a4735c0aec5c6749\",\"read-only\":false},\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"discard-no-unref\":true,\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vzsnapext/images/8006/snap2-vm-8006-disk-0.qcow2\",\"node-name\":\"ec0289317073959d450248d8cd7a480\",\"read-only\":false},\"node-name\":\"fc0289317073959d450248d8cd7a480\",\"read-only\":false},\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"discard-no-unref\":true,\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/var/lib/vzsnapext/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"e74f4959037afb46eddc7313c43dfdd\",\"read-only\":false},\"node-name\":\"f74f4959037afb46eddc7313c43dfdd\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"backing\":{\"backing\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/snap1-vm-8006-disk-0.qcow2\",\"node-name\":\"e25f58d3e6e11f2065ad41253988915\",\"read-only\":false},\"node-name\":\"f25f58d3e6e11f2065ad41253988915\",\"read-only\":false},\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/snap2-vm-8006-disk-0.qcow2\",\"node-name\":\"e9415bb5e484c1e25d25063b01686fe\",\"read-only\":false},\"node-name\":\"f9415bb5e484c1e25d25063b01686fe\",\"read-only\":false},\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0.qcow2\",\"node-name\":\"e87358a470ca311f94d5cc61d1eb428\",\"read-only\":false},\"node-name\":\"f87358a470ca311f94d5cc61d1eb428\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,scsi-id=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-balloon-free-page-reporting.conf",
    "content": "# TEST: Simple test for balloon free page reporting enabled by default on 6.2\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmachine: pc-i440fx-6.2\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-1010-1010add53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-balloon-free-page-reporting.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-1010-1010add53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -drive 'if=none,id=drive-ide2,media=cdrom,aio=io_uring' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -drive 'file=/var/lib/vz/images/8006/vm-8006-disk-0.qcow2,if=none,id=drive-scsi0,discard=on,format=qcow2,cache=none,aio=io_uring,detect-zeroes=unmap' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-i440fx-6.2+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-btrfs.conf",
    "content": "# TEST: Simple test for a BTRFS backed VM, which shouldn't use cache=none like other storages\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: btrfs-store:8006/vm-8006-disk-0.raw,discard=on,size=104858K\nscsi1: btrfs-store:8006/vm-8006-disk-0.raw,cache=writeback,discard=on,size=104858K\nscsi2: btrfs-store:8006/vm-8006-disk-0.raw,cache=writethrough,discard=on,size=104858K\nscsi3: btrfs-store:8006/vm-8006-disk-0.raw,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-1010-1010add53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-btrfs.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-1010-1010add53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/butter/bread/images/8006/vm-8006-disk-0/disk.raw\",\"node-name\":\"e99aff0ff797aa030a22e9f580076dd\",\"read-only\":false},\"node-name\":\"f99aff0ff797aa030a22e9f580076dd\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/butter/bread/images/8006/vm-8006-disk-0/disk.raw\",\"node-name\":\"e7b2fd2a8c5dbfc550d9781e5df8841\",\"read-only\":false},\"node-name\":\"f7b2fd2a8c5dbfc550d9781e5df8841\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/butter/bread/images/8006/vm-8006-disk-0/disk.raw\",\"node-name\":\"ed78b07bb04c2cbd8aedc648e885569\",\"read-only\":false},\"node-name\":\"fd78b07bb04c2cbd8aedc648e885569\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/butter/bread/images/8006/vm-8006-disk-0/disk.raw\",\"node-name\":\"e7487c01d831e2b51a5446980170ec9\",\"read-only\":false},\"node-name\":\"f7487c01d831e2b51a5446980170ec9\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=off' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-cifs.conf",
    "content": "# TEST: Simple test for a CIFS storage\nide2: none,media=cdrom\nname: simple\nostype: l26\nscsi0: cifs-store:8006/vm-8006-disk-0.raw,discard=on,size=104858K\nscsi1: cifs-store:8006/vm-8006-disk-0.raw,cache=writeback,discard=on,size=104858K\nscsi2: cifs-store:8006/vm-8006-disk-0.raw,cache=writethrough,discard=on,size=104858K\nscsi3: cifs-store:8006/vm-8006-disk-0.raw,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-pci\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-cifs.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/images/8006/vm-8006-disk-0.raw\",\"node-name\":\"e2b3b8f2d6a23adc1aa3ecd195dbaf5\",\"read-only\":false},\"node-name\":\"f2b3b8f2d6a23adc1aa3ecd195dbaf5\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/images/8006/vm-8006-disk-0.raw\",\"node-name\":\"ee4d9a961200a669c1a8182632aba3e\",\"read-only\":false},\"node-name\":\"fe4d9a961200a669c1a8182632aba3e\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/images/8006/vm-8006-disk-0.raw\",\"node-name\":\"e6a3bf7eee1e2636cbe31f62b537b6c\",\"read-only\":false},\"node-name\":\"f6a3bf7eee1e2636cbe31f62b537b6c\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/mnt/pve/cifs-store/images/8006/vm-8006-disk-0.raw\",\"node-name\":\"e7042ee58e764b1296ad54014cb9a03\",\"read-only\":false},\"node-name\":\"f7042ee58e764b1296ad54014cb9a03\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=off' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-disk-passthrough.conf",
    "content": "# TEST: Simple test for disk && cdrom passthrough\nbootdisk: scsi0\ncores: 3\nide2: cdrom,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: /dev/sda\nscsi1: /mnt/file.raw\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-disk-passthrough.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-ide2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -blockdev '{\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"driver\":\"host_cdrom\",\"filename\":\"/dev/cdrom\",\"node-name\":\"ee50e59431a6228dc388fc821b35696\",\"read-only\":true},\"node-name\":\"fe50e59431a6228dc388fc821b35696\",\"read-only\":true},\"node-name\":\"drive-ide2\",\"read-only\":true,\"throttle-group\":\"throttle-drive-ide2\"}' \\\n  -device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"host_device\",\"filename\":\"/dev/sda\",\"node-name\":\"eec235c1b362ebd19d5e98959b4c171\",\"read-only\":false},\"node-name\":\"fec235c1b362ebd19d5e98959b4c171\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"on\",\"discard\":\"ignore\",\"driver\":\"file\",\"filename\":\"/mnt/file.raw\",\"node-name\":\"e234a4e3b89ac3adac9bdbf0c3dd6b4\",\"read-only\":false},\"node-name\":\"f234a4e3b89ac3adac9bdbf0c3dd6b4\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-lvm.conf",
    "content": "# TEST: Simple test for LVM backed VM\nbootdisk: scsi0\nname: simple\nscsi0: lvm-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: lvm-store:vm-8006-disk-0,cache=writeback,discard=on,size=104858K\nscsi2: lvm-store:vm-8006-disk-0,cache=writethrough,discard=on,size=104858K\nscsi3: lvm-store:vm-8006-disk-0,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-pci\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-lvm.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"f0378a375d635b0f473569544c7c207\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e2fbae024c8a771f708f4a5391211b0\",\"read-only\":false},\"node-name\":\"f2fbae024c8a771f708f4a5391211b0\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e4328c26b141e3efe1564cb60bf1155\",\"read-only\":false},\"node-name\":\"f4328c26b141e3efe1564cb60bf1155\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"native\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/veegee/vm-8006-disk-0\",\"node-name\":\"e68e10f8128f05fe5f7e85cc1f9922b\",\"read-only\":false},\"node-name\":\"f68e10f8128f05fe5f7e85cc1f9922b\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-lvmthin.conf",
    "content": "# TEST: Simple test for LVMthin backed VM\nbootdisk: scsi0\nname: simple\nscsi0: local-lvm:vm-8006-disk-0,discard=on,size=104858K\nscsi1: local-lvm:vm-8006-disk-0,cache=writeback,discard=on,size=104858K\nscsi2: local-lvm:vm-8006-disk-0,cache=writethrough,discard=on,size=104858K\nscsi3: local-lvm:vm-8006-disk-0,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-pci\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-lvmthin.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/pve/vm-8006-disk-0\",\"node-name\":\"e6d87b01b7bb888b8426534a542ff1c\",\"read-only\":false},\"node-name\":\"f6d87b01b7bb888b8426534a542ff1c\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/pve/vm-8006-disk-0\",\"node-name\":\"e96d9ece81aa4271aa2d8485184f66b\",\"read-only\":false},\"node-name\":\"f96d9ece81aa4271aa2d8485184f66b\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/pve/vm-8006-disk-0\",\"node-name\":\"e0b89788ef97beda10a850ab45897d9\",\"read-only\":false},\"node-name\":\"f0b89788ef97beda10a850ab45897d9\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/pve/vm-8006-disk-0\",\"node-name\":\"ea7b6871af66ca3e13e95bd74570aa2\",\"read-only\":false},\"node-name\":\"fa7b6871af66ca3e13e95bd74570aa2\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=off' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-rbd.conf",
    "content": "# TEST: Simple test for RBD && KRBD backend vm\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: rbd-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: rbd-store:vm-8006-disk-0,discard=on,cache=writeback,size=104858K\nscsi2: rbd-store:vm-8006-disk-0,discard=on,cache=writethrough,size=104858K\nscsi3: rbd-store:vm-8006-disk-0,discard=on,cache=directsync,size=104858K\nscsi4: krbd-store:vm-8006-disk-0,discard=on,size=104858K\nscsi5: krbd-store:vm-8006-disk-0,cache=writeback,discard=on,size=104858K\nscsi6: krbd-store:vm-8006-disk-0,cache=writethrough,discard=on,size=104858K\nscsi7: krbd-store:vm-8006-disk-0,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-1010-1010add53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-rbd.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi4\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi5\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi6\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi7\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-1010-1010add53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"rbd\",\"image\":\"vm-8006-disk-0\",\"node-name\":\"e8e1af6f55c6a2466f178045aa79710\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"f8e1af6f55c6a2466f178045aa79710\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"rbd\",\"image\":\"vm-8006-disk-0\",\"node-name\":\"e3990bba2ed1f48c5bb23e9f37b4cec\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"f3990bba2ed1f48c5bb23e9f37b4cec\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"rbd\",\"image\":\"vm-8006-disk-0\",\"node-name\":\"e3beccc2a8f2eacb8b5df8055a7d093\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"f3beccc2a8f2eacb8b5df8055a7d093\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"auth-client-required\":[\"none\"],\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"rbd\",\"image\":\"vm-8006-disk-0\",\"node-name\":\"eef923d5dfcee93fbc712b03f9f21af\",\"pool\":\"cpool\",\"read-only\":false,\"server\":[{\"host\":\"127.0.0.42\",\"port\":\"3300\"},{\"host\":\"127.0.0.21\",\"port\":\"3300\"},{\"host\":\"::1\",\"port\":\"3300\"}]},\"node-name\":\"fef923d5dfcee93fbc712b03f9f21af\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-0\",\"node-name\":\"eb2c7a292f03b9f6d015cf83ae79730\",\"read-only\":false},\"node-name\":\"fb2c7a292f03b9f6d015cf83ae79730\",\"read-only\":false},\"node-name\":\"drive-scsi4\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi4\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=4,drive=drive-scsi4,id=scsi4,device_id=drive-scsi4,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-0\",\"node-name\":\"e5258ec75558b1f102af1e20e677fd0\",\"read-only\":false},\"node-name\":\"f5258ec75558b1f102af1e20e677fd0\",\"read-only\":false},\"node-name\":\"drive-scsi5\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi5\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=5,drive=drive-scsi5,id=scsi5,device_id=drive-scsi5,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"threads\",\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-0\",\"node-name\":\"edb33cdcea8ec3e2225509c4945227e\",\"read-only\":false},\"node-name\":\"fdb33cdcea8ec3e2225509c4945227e\",\"read-only\":false},\"node-name\":\"drive-scsi6\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi6\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=6,drive=drive-scsi6,id=scsi6,device_id=drive-scsi6,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"host_device\",\"filename\":\"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/cpool/vm-8006-disk-0\",\"node-name\":\"eb0b017124a47505c97a5da052e0141\",\"read-only\":false},\"node-name\":\"fb0b017124a47505c97a5da052e0141\",\"read-only\":false},\"node-name\":\"drive-scsi7\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi7\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=7,drive=drive-scsi7,id=scsi7,device_id=drive-scsi7,write-cache=off' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-virtio-blk.conf",
    "content": "# TEST: Test for a basic configuration with a VirtIO Block IOThread disk\nbootdisk: virtio0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvirtio0: local:8006/vm-8006-disk-0.qcow2,discard=on,iothread=1,size=104858K\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-virtio-blk.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object 'iothread,id=iothread-virtio0' \\\n  -object '{\"id\":\"throttle-drive-virtio0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"edd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"fdd19f6c1b3a6d5a6248c3376a91a16\",\"read-only\":false},\"node-name\":\"drive-virtio0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-virtio0\"}' \\\n  -device 'virtio-blk-pci,drive=drive-virtio0,id=virtio0,bus=pci.0,addr=0xa,iothread=iothread-virtio0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-zfs-over-iscsi.conf",
    "content": "# TEST: Simple test for zfs-over-scsi backed VM.\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: zfs-over-iscsi-store:vm-8006-disk-0,discard=on,size=104858K\nscsi1: zfs-over-iscsi-store:vm-8006-disk-0,cache=writeback,discard=on,size=104858K\nscsi2: zfs-over-iscsi-store:vm-8006-disk-0,cache=writethrough,discard=on,size=104858K\nscsi3: zfs-over-iscsi-store:vm-8006-disk-0,cache=directsync,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-1010-1010add53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple-zfs-over-iscsi.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi1\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi2\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-scsi3\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-1010-1010add53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"iscsi\",\"lun\":0,\"node-name\":\"e7106ac43d4f125a1911487dd9e3e42\",\"portal\":\"127.0.0.1\",\"read-only\":false,\"target\":\"iqn.2019-10.org.test:foobar\",\"transport\":\"tcp\"},\"node-name\":\"f7106ac43d4f125a1911487dd9e3e42\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"iscsi\",\"lun\":0,\"node-name\":\"efdb73e0d0acc5a60e3ff438cb20113\",\"portal\":\"127.0.0.1\",\"read-only\":false,\"target\":\"iqn.2019-10.org.test:foobar\",\"transport\":\"tcp\"},\"node-name\":\"ffdb73e0d0acc5a60e3ff438cb20113\",\"read-only\":false},\"node-name\":\"drive-scsi1\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi1\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=1,drive=drive-scsi1,id=scsi1,device_id=drive-scsi1,write-cache=on' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"cache\":{\"direct\":false,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"iscsi\",\"lun\":0,\"node-name\":\"eab527a81b458aa9603dca5e2505f6e\",\"portal\":\"127.0.0.1\",\"read-only\":false,\"target\":\"iqn.2019-10.org.test:foobar\",\"transport\":\"tcp\"},\"node-name\":\"fab527a81b458aa9603dca5e2505f6e\",\"read-only\":false},\"node-name\":\"drive-scsi2\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi2\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=2,drive=drive-scsi2,id=scsi2,device_id=drive-scsi2,write-cache=off' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"raw\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"iscsi\",\"lun\":0,\"node-name\":\"e915a332310039f7a3feed6901eb5da\",\"portal\":\"127.0.0.1\",\"read-only\":false,\"target\":\"iqn.2019-10.org.test:foobar\",\"transport\":\"tcp\"},\"node-name\":\"f915a332310039f7a3feed6901eb5da\",\"read-only\":false},\"node-name\":\"drive-scsi3\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi3\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=3,drive=drive-scsi3,id=scsi3,device_id=drive-scsi3,write-cache=off' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/simple1-template.conf",
    "content": "# TEST: Simple test for a basic template configuration\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nsata0: local:8006/base-8006-disk-0.qcow2,discard=on,size=104858K\nscsi0: local:8006/base-8006-disk-1.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\ntemplate: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple1-template.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vga none \\\n  -nographic \\\n  -cpu qemu64 \\\n  -m 512 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -object '{\"id\":\"throttle-drive-sata0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/base-8006-disk-1.qcow2\",\"node-name\":\"e1085774206ae4a6b6bf8426ff08f16\",\"read-only\":true},\"node-name\":\"f1085774206ae4a6b6bf8426ff08f16\",\"read-only\":true},\"node-name\":\"drive-scsi0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,write-cache=on' \\\n  -device 'ahci,id=ahci0,multifunction=on,bus=pci.0,addr=0x7' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/base-8006-disk-0.qcow2\",\"node-name\":\"eab334c2e07734480f33dd80d89871b\",\"read-only\":true},\"node-name\":\"fab334c2e07734480f33dd80d89871b\",\"read-only\":true},\"node-name\":\"drive-sata0\",\"read-only\":true,\"throttle-group\":\"throttle-drive-sata0\"}' \\\n  -device 'ide-cd,bus=ahci0.0,drive=drive-sata0,id=sata0,write-cache=on' \\\n  -machine 'accel=tcg,smm=off,type=pc+pve0' \\\n  -snapshot\n"
  },
  {
    "path": "src/test/cfg2cmd/simple1.conf",
    "content": "# TEST: Simple test for a basic configuration with no special things\nbootdisk: scsi0\ncores: 3\nide2: none,media=cdrom\nmemory: 768\nname: simple\nnet0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0\nnuma: 0\nostype: l26\nscsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nsockets: 1\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\n"
  },
  {
    "path": "src/test/cfg2cmd/simple1.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'simple,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '3,sockets=1,cores=3,maxcpus=3' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -object '{\"id\":\"throttle-drive-scsi0\",\"limits\":{},\"qom-type\":\"throttle-group\"}' \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \\\n  -device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \\\n  -blockdev '{\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"throttle\",\"file\":{\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"qcow2\",\"file\":{\"aio\":\"io_uring\",\"cache\":{\"direct\":true,\"no-flush\":false},\"detect-zeroes\":\"unmap\",\"discard\":\"unmap\",\"driver\":\"file\",\"filename\":\"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2\",\"node-name\":\"ecd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"fcd04be4259153b8293415fefa2a84c\",\"read-only\":false},\"node-name\":\"drive-scsi0\",\"read-only\":false,\"throttle-group\":\"throttle-drive-scsi0\"}' \\\n  -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-enhancments.conf",
    "content": "# TEST: Test for SPICE enhancements\nmachine: pc-i440fx-4.0\nsmbios1: uuid=363a6126-5f48-43e1-811f-013294a946a0\nspice_enhancements: foldersharing=1,videostreaming=all\nvga: qxl\nvmgenid: 719b9591-1b0d-4e43-bca2-22b9ffbd3568\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-enhancments.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=363a6126-5f48-43e1-811f-013294a946a0' \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=719b9591-1b0d-4e43-bca2-22b9ffbd3568' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'qxl-vga,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -chardev 'spiceport,id=foldershare,name=org.spice-space.webdav.0' \\\n  -device 'virtserialport,chardev=foldershare,name=org.spice-space.webdav.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on,streaming-video=all' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc-i440fx-4.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-linux-4.1.conf",
    "content": "# TEST: Test for SPICE with SPICE with max_outputs\ncores: 2\nmachine: pc-i440fx-4.1\nmemory: 768\nname: spicelinux\nnet0: virtio=A2:C0:43:67:08:A1,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-linux-4.1.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'spicelinux,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'qxl-vga,id=vga,max_outputs=4,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:67:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-i440fx-4.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-usb3.conf",
    "content": "# TEST: Test for SPICE with a USB3 based SPICE port added\ncores: 2\nmachine: pc-i440fx-4.0\nmemory: 768\nname: spiceusb3\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb1: spice,usb3=1\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-usb3.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'spiceusb3,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'nec-usb-xhci,id=xhci,bus=pci.1,addr=0x1b' \\\n  -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0' \\\n  -device 'qxl-vga,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -machine 'type=pc-i440fx-4.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-win.conf",
    "content": "# TEST: Test for SPICE under Win10 with a USB3 based SPICE port added\ncores: 2\nmachine: pc-i440fx-4.0\nmemory: 768\nname: spiceusb3\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: win10\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec461\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf7\nvga: qxl2\nusb1: spice,usb3=1\n"
  },
  {
    "path": "src/test/cfg2cmd/spice-win.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'spiceusb3,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec461' \\\n  -smp '2,sockets=1,cores=2,maxcpus=2' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \\\n  -m 768 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf7' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'nec-usb-xhci,id=xhci,bus=pci.1,addr=0x1b' \\\n  -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \\\n  -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0' \\\n  -device 'qxl-vga,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'qxl,id=vga1,ram_size=67108864,vram_size=33554432,bus=pci.0,addr=0x18' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'spicevmc,id=vdagent,name=vdagent' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \\\n  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \\\n  -rtc 'driftfix=slew,base=localtime' \\\n  -machine 'hpet=off,type=pc-i440fx-4.0+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/startdate-l26.conf",
    "content": "# TEST: Simple test for startdate parameter with Linux\nostype: l26\nstartdate: 2006-06-17T16:01:21\n"
  },
  {
    "path": "src/test/cfg2cmd/startdate-l26.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -rtc 'base=2006-06-17T16:01:21' \\\n  -machine 'hpet=off,type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/startdate-win11.conf",
    "content": "# TEST: Simple test for startdate parameter with Windows and explicitly disabled time drift fix\nostype: win11\ntdf: 0\nstartdate: 2020-01-11\n"
  },
  {
    "path": "src/test/cfg2cmd/startdate-win11.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -global 'kvm-pit.lost_tick_policy=discard' \\\n  -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \\\n  -m 512 \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2,edid=off' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -rtc 'base=2020-01-11' \\\n  -machine 'hpet=off,type=pc-i440fx-5.1+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/unsupported-storage-content-type.conf",
    "content": "# TEST: Unsupported storage content type in a volume disk\n# EXPECT_ERROR: storage 'noimages' does not support content-type 'images'\nscsi0: noimages:8006/vm-8006-disk-0.raw,iothread=1,size=32G\n"
  },
  {
    "path": "src/test/cfg2cmd/usb13-error.conf",
    "content": "# TEST: Test error for old machine type with newer usb config\n# EXPECT_ERROR: using usb13 is only possible with machine type >= 7.1 and ostype l26 or windows > 7\ncores: 2\nmachine: pc-i440fx-4.0\nmemory: 768\nname: q35-usb3-error\nnet0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0\nostype: l26\nscsihw: virtio-scsi-pci\nsmbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465\nvmgenid: c773c261-d800-4348-9f5d-167fadd53cf8\nvga: qxl\nusb13: spice\n"
  },
  {
    "path": "src/test/cfg2cmd/vnc-clipboard-spice.conf",
    "content": "# TEST: Test for a VNC clipboard with a SPICE QXL display\nvga: qxl,clipboard=vnc\n"
  },
  {
    "path": "src/test/cfg2cmd/vnc-clipboard-spice.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'qxl-vga,id=vga,max_outputs=4,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'qemu-vdagent,id=vdagent,name=vdagent,clipboard=on' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/cfg2cmd/vnc-clipboard-std.conf",
    "content": "# TEST: Test for a VNC clipboard with a std display\nvga: std,clipboard=vnc\n"
  },
  {
    "path": "src/test/cfg2cmd/vnc-clipboard-std.conf.cmd",
    "content": "/usr/bin/kvm \\\n  -id 8006 \\\n  -name 'vm8006,debug-threads=on' \\\n  -no-shutdown \\\n  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \\\n  -mon 'chardev=qmp,mode=control' \\\n  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \\\n  -mon 'chardev=qmp-event,mode=control' \\\n  -pidfile /var/run/qemu-server/8006.pid \\\n  -daemonize \\\n  -smp '1,sockets=1,cores=1,maxcpus=1' \\\n  -nodefaults \\\n  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \\\n  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \\\n  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \\\n  -m 512 \\\n  -global 'PIIX4_PM.disable_s3=1' \\\n  -global 'PIIX4_PM.disable_s4=1' \\\n  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \\\n  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \\\n  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \\\n  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \\\n  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \\\n  -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \\\n  -chardev 'qemu-vdagent,id=vdagent,name=vdagent,clipboard=on' \\\n  -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \\\n  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \\\n  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \\\n  -machine 'type=pc+pve0'\n"
  },
  {
    "path": "src/test/parse-config-expected/cloudinit-snapshot.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: cloudinit\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[special:cloudinit]\nipconfig0: ip=dhcp,ip6=dhcp\nname: deb122\n\n[cloudinit]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737549549\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-expected/cloudinit-snapshot.conf.strict.error",
    "content": "vm 8006 - unable to parse value of 'numa' - type check ('boolean') failed - got 'verify meee~ :)'\n"
  },
  {
    "path": "src/test/parse-config-expected/duplicate-sections.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb122\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: foo\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[PENDING]\nvga: qxl\n\n[special:cloudinit]\nname: deb123\n\n[foo]\nboot: order=scsi0\ncores: 4\ncpu: host\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737548747\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-expected/duplicate-sections.conf.strict.error",
    "content": "vm 8006 - duplicate section: pending\n"
  },
  {
    "path": "src/test/parse-config-expected/unknown-sections.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: foo\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[PENDING]\nbios: ovmf\n\n[special:cloudinit]\nipconfig0: ip=dhcp,ip6=dhcp\nname: deb122\n\n[foo]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737548747\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-expected/unknown-sections.conf.strict.error",
    "content": "vm 8006 - skipping unknown section: 'special:unknown123'\n"
  },
  {
    "path": "src/test/parse-config-expected/verify-snapshot.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: snap\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[snap]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737549549\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-expected/verify-snapshot.conf.strict.error",
    "content": "vm 8006 - unable to parse value of 'numa' - type check ('boolean') failed - got 'verify meee~ :)'\n"
  },
  {
    "path": "src/test/parse-config-input/cloudinit-snapshot.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: cloudinit\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[special:cloudinit]\nipconfig0: ip=dhcp,ip6=dhcp\nname: deb122\n\n[cloudinit]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: verify meee~ :)\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737549549\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-input/duplicate-sections.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb122\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: foo\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[PENDING]\nbios: ovmf\n\n[PENDING]\nvga: qxl\n\n[special:cloudinit]\nname: deb12\n\n[special:cloudinit]\nname: deb123\n\n[foo]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737548747\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[foo]\nboot: order=scsi0\ncores: 4\ncpu: host\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737548747\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-input/fleecing-section.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[special:fleecing]\nfleecing-images: zfs:vm-120-fleece-0\n"
  },
  {
    "path": "src/test/parse-config-input/locked.conf",
    "content": "# locked\nbootdisk: scsi0\ncores: 1\nide2: none,media=cdrom\nlock: backup\nmemory: 512\nname: apache\nnet0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: mydir:1422/vm-1422-disk-0.qcow2,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd\nsockets: 1\nunused7: mydir:1422/vm-1422-disk-8.qcow2\nvmgenid: 0\n"
  },
  {
    "path": "src/test/parse-config-input/plain.conf",
    "content": "# plain VM\nbootdisk: scsi0\ncores: 1\nide2: none,media=cdrom\nmemory: 512\nname: apache\nnet0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: mydir:142/vm-142-disk-0.qcow2,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd\nsockets: 1\ntags: foo bar\nvmgenid: 0\n"
  },
  {
    "path": "src/test/parse-config-input/regular-vm-efi.conf",
    "content": "# regular VM with an EFI disk\nbios: ovmf\nboot: order=scsi0;ide2;net0\ncores: 1\nefidisk0: mydir:139/vm-139-disk-0.qcow2,size=128K\nide2: local:iso/debian-10.6.0-amd64-netinst.iso,media=cdrom\nmemory: 2048\nname: eficloneclone\nnet0: virtio=7A:6C:A5:8B:11:93,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: rbdkvm:vm-139-disk-1,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=21a7e7bc-3cd2-4232-a009-a41f4ee992ae\nsockets: 1\nvmgenid: 0\n"
  },
  {
    "path": "src/test/parse-config-input/sections.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: foo\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[PENDING]\nbios: ovmf\n\n[special:cloudinit]\nipconfig0: ip=dhcp,ip6=dhcp\nname: deb122\n\n[foo]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737548747\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/parse-config-input/snapshots.conf",
    "content": "boot: order=scsi1;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nide0: dir:111/vm-111-disk-2.qcow2,size=1G\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2019-evaluation.iso,media=cdrom,size=4985424K\nmachine: pc-i440fx-9.1\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnet1: e1000=BC:24:11:79:D5:65,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: win19_5_2_plus_stuff\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsi1: lvmthinbig:vm-111-disk-0,iothread=1,size=32G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsockets: 1\nunused0: rbd:vm-111-disk-0\nvga: qxl\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: 713da648-38a6-489e-b0b2-dd9cef419f33\n\n[machine_version_5_1]\nboot: order=ide0;ide2;net0\ncores: 4\ncpu: x86-64-v2-AES\nide0: lvmthinbig:vm-111-disk-0,size=32G\nide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: e1000=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736939109\nsockets: 1\nvmgenid: 1f314a76-50a3-4b92-9307-c8c6e313d3ca\n\n[machine_version_5_1_with_virtio]\nboot: order=ide0;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nide0: lvmthinbig:vm-111-disk-0,size=32G\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: machine_version_5_1\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736940462\nsockets: 1\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: 4f602356-cb9c-45ad-a554-d76d95c7c0f8\n\n[ovmf_machine_version_5_1]\nbios: ovmf\nboot: order=ide0;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nefidisk0: rbd:vm-111-disk-0,efitype=4m,pre-enrolled-keys=1,size=1M\nide0: lvmthinbig:vm-111-disk-0,size=32G\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K\nmachine: pc-q35-5.1\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: e1000=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: machine_version_5_1_with_virtio\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736943308\nsockets: 1\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: 4f602356-cb9c-45ad-a554-d76d95c7c0f8\n\n[ovmf_machine_version_5_1_virtio]\nbios: ovmf\nboot: order=ide0;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nefidisk0: rbd:vm-111-disk-0,efitype=4m,pre-enrolled-keys=1,size=1M\nide0: lvmthinbig:vm-111-disk-0,size=32G\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K\nmachine: pc-q35-5.1\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: ovmf_machine_version_5_1\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736944525\nsockets: 1\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: 00b95468-4f34-4faa-b0af-b214ff5bbcdf\n\n[static-network]\nbios: ovmf\nboot: order=ide0;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nefidisk0: rbd:vm-111-disk-0,efitype=4m,pre-enrolled-keys=1,size=1M\nide0: lvmthinbig:vm-111-disk-0,size=32G\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K\nmachine: pc-q35-5.1\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: ovmf_machine_version_5_1_virtio\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736945713\nsockets: 1\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: 5d65fc62-2cb1-4945-9641-631b37c265a5\n\n[win19_5_2]\nboot: order=scsi1;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2019-evaluation.iso,media=cdrom,size=4985424K\nmachine: pc-i440fx-5.2\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnet1: e1000=BC:24:11:79:D5:65,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: machine_version_5_1_with_virtio\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsi1: lvmthinbig:vm-111-disk-0,iothread=1,size=32G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736950690\nsockets: 1\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: f259de06-fa08-4ff7-8ba9-b1233a726ac4\n\n[win19_5_2_plus_stuff]\nboot: order=scsi1;ide2;net0;ide1\ncores: 4\ncpu: x86-64-v2-AES\nide0: dir:111/vm-111-disk-2.qcow2,size=1G\nide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K\nide2: sani:iso/Win2019-evaluation.iso,media=cdrom,size=4985424K\nmachine: pc-i440fx-5.2\nmemory: 4096\nmeta: creation-qemu=9.1.2,ctime=1736349024\nname: win-machine-ver\nnet0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1\nnet1: e1000=BC:24:11:79:D5:65,bridge=vnet0,firewall=1\nnuma: 0\nostype: win10\nparent: win19_5_2\nscsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G\nscsi1: lvmthinbig:vm-111-disk-0,iothread=1,size=32G\nscsihw: virtio-scsi-single\nsmbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658\nsnaptime: 1736951300\nsockets: 1\nvga: qxl\nvirtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G\nvmgenid: 713da648-38a6-489e-b0b2-dd9cef419f33\n"
  },
  {
    "path": "src/test/parse-config-input/unknown-sections.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: foo\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[special:unknown123]\nname: foo\n\n[PENDING]\nbios: ovmf\n\n[special:unknown124]\nbios: seabios\n\n[special:cloudinit]\nipconfig0: ip=dhcp,ip6=dhcp\nname: deb122\n\n[special:unknown125]\nname: bar\n\n[foo]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip=dhcp,ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737548747\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[:3]\nname: baz\ncat: nya~\n"
  },
  {
    "path": "src/test/parse-config-input/verify-snapshot.conf",
    "content": "boot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: 0\nostype: l26\nparent: snap\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsockets: 1\nunused0: rbd:vm-120-disk-0\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n\n[snap]\nboot: order=scsi0\ncores: 2\ncpu: x86-64-v2-AES\nide2: lvm:vm-120-cloudinit,media=cdrom\nipconfig0: ip6=dhcp\nmemory: 4096\nmeta: creation-qemu=9.0.2,ctime=1725975013\nname: deb1223\nnet0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1\nnuma: verify meee~ :)\nostype: l26\nscsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G\nscsihw: virtio-scsi-single\nsmbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746\nsnaptime: 1737549549\nsockets: 1\nvmgenid: 7079e97c-50e3-4079-afe7-23e67566b946\n"
  },
  {
    "path": "src/test/restore-config-expected/139.conf",
    "content": "# regular VM with an EFI disk\nbios: ovmf\nboot: order=scsi0;ide2;net0\ncores: 1\nefidisk0: target:139/vm-139-disk-0.qcow2,size=128K\nide2: local:iso/debian-10.6.0-amd64-netinst.iso,media=cdrom\nmemory: 2048\nname: eficloneclone\nnet0: virtio=7A:6C:A5:8B:11:93,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: target:139/vm-139-disk-1.raw,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=21a7e7bc-3cd2-4232-a009-a41f4ee992ae\nsockets: 1\nvmgenid: 0\n"
  },
  {
    "path": "src/test/restore-config-expected/142.conf",
    "content": "# plain VM\nbootdisk: scsi0\ncores: 1\nide2: none,media=cdrom\nmemory: 512\nname: apache\nnet0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: target:142/vm-142-disk-0.qcow2,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd\nsockets: 1\nvmgenid: 0\ntags: foo bar\n"
  },
  {
    "path": "src/test/restore-config-expected/1422.conf",
    "content": "# some properties should be filtered\nbootdisk: scsi0\ncores: 1\nide2: none,media=cdrom\nmemory: 512\nname: apache\nnet0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: target:1422/vm-1422-disk-0.qcow2,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd\nsockets: 1\nvmgenid: 0\n"
  },
  {
    "path": "src/test/restore-config-expected/179.conf",
    "content": "# many disks\nboot: order=scsi0;ide2;net0\ncores: 1\nide2: none,media=cdrom\nmemory: 2048\nnet0: virtio=26:15:5B:73:3F:7C,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: target:179/vm-179-disk-0.qcow2,cache=none,discard=on,size=32G,ssd=1\nscsi1: target:179/vm-179-disk-1.qcow2,cache=writethrough,size=32G\nscsi2: target:179/vm-179-disk-2.qcow2,mbps_rd=7,mbps_wr=7,replicate=0,size=32G\nscsi3: target:179/vm-179-disk-3.vmdk,size=32G\n#scsi4: myfs:179/vm-179-disk-1.qcow2,backup=0,size=32G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=1819ead7-a55d-4544-8d38-29ca94869a9c\nsockets: 1\nvmgenid: 0\n"
  },
  {
    "path": "src/test/restore-config-input/139.conf",
    "content": "# regular VM with an EFI disk\nbios: ovmf\nboot: order=scsi0;ide2;net0\ncores: 1\nefidisk0: mydir:139/vm-139-disk-0.qcow2,size=128K\nide2: local:iso/debian-10.6.0-amd64-netinst.iso,media=cdrom\nmemory: 2048\nname: eficloneclone\nnet0: virtio=7A:6C:A5:8B:11:93,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: rbdkvm:vm-139-disk-1,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=21a7e7bc-3cd2-4232-a009-a41f4ee992ae\nsockets: 1\nvmgenid: 0\n#qmdump#map:efidisk0:drive-efidisk0:mydir:qcow2:\n#qmdump#map:scsi0:drive-scsi0:rbdkvm::\n"
  },
  {
    "path": "src/test/restore-config-input/142.conf",
    "content": "# plain VM\nbootdisk: scsi0\ncores: 1\nide2: none,media=cdrom\nmemory: 512\nname: apache\nnet0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: mydir:142/vm-142-disk-0.qcow2,size=4G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd\nsockets: 1\nvmgenid: 0\ntags: foo bar\n#qmdump#map:scsi0:drive-scsi0:mydir:qcow2:\n"
  },
  {
    "path": "src/test/restore-config-input/1422.conf",
    "content": "# some properties should be filtered\nbootdisk: scsi0\ncores: 1\nide2: none,media=cdrom\nmemory: 512\nname: apache\nnet0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: mydir:1422/vm-1422-disk-0.qcow2,size=4G\nunused7: mydir:1422/vm-1422-disk-8.qcow2\nparent: snap\nlock: backup\nscsihw: virtio-scsi-pci\nsmbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd\nsockets: 1\nvmgenid: 0\n#qmdump#map:scsi0:drive-scsi0:mydir:qcow2:\n"
  },
  {
    "path": "src/test/restore-config-input/179.conf",
    "content": "# many disks\nboot: order=scsi0;ide2;net0\ncores: 1\nide2: none,media=cdrom\nmemory: 2048\nnet0: virtio=26:15:5B:73:3F:7C,bridge=vmbr0,firewall=1\nnuma: 0\nostype: l26\nscsi0: myfs:179/vm-179-disk-4.qcow2,cache=none,discard=on,size=32G,ssd=1\nscsi1: myfs:179/vm-179-disk-0.qcow2,cache=writethrough,size=32G\nscsi2: myfs:179/vm-179-disk-2.qcow2,mbps_rd=7,mbps_wr=7,replicate=0,size=32G\nscsi3: myfs:179/vm-179-disk-3.vmdk,size=32G\nscsi4: myfs:179/vm-179-disk-1.qcow2,backup=0,size=32G\nscsihw: virtio-scsi-pci\nsmbios1: uuid=1819ead7-a55d-4544-8d38-29ca94869a9c\nsockets: 1\nvmgenid: 0\n#qmdump#map:scsi0:drive-scsi0:myfs:qcow2:\n#qmdump#map:scsi1:drive-scsi1:myfs:qcow2:\n#qmdump#map:scsi2:drive-scsi2:myfs:qcow2:\n#qmdump#map:scsi3:drive-scsi3:myfs:vmdk:\n"
  },
  {
    "path": "src/test/run_config2command_tests.pl",
    "content": "#!/usr/bin/perl\n\nuse v5.36;\n\nuse lib qw(..);\n\nuse JSON qw(decode_json);\nuse Test::More;\nuse Test::MockModule;\nuse Socket qw(AF_INET AF_INET6);\n\nuse PVE::File qw(file_get_contents file_set_contents);\nuse PVE::Tools qw(run_command);\nuse PVE::INotify;\nuse PVE::SysFSTools;\n\nuse PVE::QemuConfig;\nuse PVE::QemuServer;\nuse PVE::QemuServer::Drive;\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::Monitor;\nuse PVE::QemuServer::OVMF;\nuse PVE::QemuServer::QMPHelpers;\nuse PVE::QemuServer::CPUConfig;\nuse PVE::Storage;\n\nmy $base_env = {\n    storage_config => {\n        ids => {\n            local => {\n                content => {\n                    images => 1,\n                    iso => 1,\n                },\n                path => '/var/lib/vz',\n                type => 'dir',\n                shared => 0,\n            },\n            localsnapext => {\n                content => {\n                    images => 1,\n                },\n                path => '/var/lib/vzsnapext',\n                type => 'dir',\n                shared => 0,\n                'snapshot-as-volume-chain' => 1,\n            },\n            noimages => {\n                content => {\n                    iso => 1,\n                },\n                path => '/var/lib/vz',\n                type => 'dir',\n            },\n            'btrfs-store' => {\n                content => {\n                    images => 1,\n                },\n                path => '/butter/bread',\n                type => 'btrfs',\n            },\n            'cifs-store' => {\n                shared => 1,\n                path => '/mnt/pve/cifs-store',\n                username => 'guest',\n                server => '127.0.0.42',\n                type => 'cifs',\n                share => 'CIFShare',\n                content => {\n                    images => 1,\n                    iso => 1,\n                },\n            },\n            'rbd-store' => {\n                monhost => '127.0.0.42,127.0.0.21,::1',\n                fsid => 'fc4181a6-56eb-4f68-b452-8ba1f381ca2a',\n                content => {\n                    images => 1,\n                },\n                type => 'rbd',\n                pool => 'cpool',\n                username => 'admin',\n                shared => 1,\n            },\n            'krbd-store' => {\n                monhost => '127.0.0.42,127.0.0.21,::1',\n                fsid => 'fc4181a6-56eb-4f68-b452-8ba1f381ca2a',\n                content => {\n                    images => 1,\n                },\n                type => 'rbd',\n                pool => 'cpool',\n                username => 'admin',\n                shared => 1,\n                krbd => 1,\n            },\n            'zfs-over-iscsi-store' => {\n                type => 'zfs',\n                iscsiprovider => \"comstar\",\n                lio_tpg => \"tpg1\",\n                portal => \"127.0.0.1\",\n                target => \"iqn.2019-10.org.test:foobar\",\n                pool => \"tank\",\n                content => {\n                    images => 1,\n                },\n            },\n            'lvm-store' => {\n                vgname => 'veegee',\n                type => 'lvm',\n                content => {\n                    images => 1,\n                },\n            },\n            'local-lvm' => {\n                vgname => 'pve',\n                bwlimit => 'restore=1024',\n                type => 'lvmthin',\n                thinpool => 'data',\n                content => {\n                    images => 1,\n                },\n            },\n        },\n    },\n    vmid => 8006,\n    real_qemu_version => PVE::QemuServer::Helpers::kvm_user_version(), # not yet mocked\n};\n\nmy $pci_devs = [\n    \"0000:00:43.1\",\n    \"0000:00:f4.0\",\n    \"0000:00:ff.1\",\n    \"0000:0f:f2.0\",\n    \"0000:d0:13.0\",\n    \"0000:d0:15.1\",\n    \"0000:d0:15.2\",\n    \"0000:d0:17.0\",\n    \"0000:f0:42.0\",\n    \"0000:f0:43.0\",\n    \"0000:f0:43.1\",\n    \"1234:f0:43.1\",\n    \"0000:01:00.4\",\n    \"0000:01:00.5\",\n    \"0000:01:00.6\",\n    \"0000:07:10.0\",\n    \"0000:07:10.1\",\n    \"0000:07:10.4\",\n];\n\nmy $pci_map_config = {\n    ids => {\n        someGpu => {\n            type => 'pci',\n            mdev => 1,\n            map => [\n                'node=localhost,path=0000:01:00.4,id=10de:2231,iommugroup=1',\n                'node=localhost,path=0000:01:00.5,id=10de:2231,iommugroup=1',\n                'node=localhost,path=0000:01:00.6,id=10de:2231,iommugroup=1',\n            ],\n        },\n        someNic => {\n            type => 'pci',\n            map => [\n                'node=localhost,path=0000:07:10.0,id=8086:1520,iommugroup=2',\n                'node=localhost,path=0000:07:10.1,id=8086:1520,iommugroup=2',\n                'node=localhost,path=0000:07:10.4,id=8086:1520,iommugroup=2',\n            ],\n        },\n    },\n};\n\nmy $usb_map_config = {},\n\n    my $cpu_hw_capabilities = {\n        # Gathered from an AMD EPYC 9475F running kernel 6.11.11-2-pve\n        'amd-turin-9005' =>\n        '{ \"amd-sev\": { \"cbitpos\": 51, \"reduced-phys-bits\": 6, \"sev-support\": true,'\n        . ' \"sev-support-es\": true, \"sev-support-snp\": true } }',\n        # TODO: others?\n    };\n\nmy $current_test; # = {\n#   description => 'Test description', # if available\n#   qemu_version => '2.12',\n#   host_arch => 'HOST_ARCH',\n#   expected_error => 'error message',\n#   expected_warning => 'warning message',\n#   config => { config hash },\n#   expected => [ expected outcome cmd line array ],\n# };\n\n# use the config description to allow changing environment, fields are:\n#   TEST: A single line describing the test, gets outputted\n#   QEMU_VERSION: \\d+\\.\\d+(\\.\\d+)? (defaults to current version)\n#   HOST_ARCH: x86_64 | aarch64 (default to x86_64, to make tests stable)\n#   EXPECT_ERROR: <error message> For negative tests\n#   EXPECT_WARN(ING): <warning message> that is expected\n#   HW_CAPABILITIES: <cpu-type-or-json-string> to defined the HW caps the test should expose\n# all fields are optional\nsub parse_test($config_fn) {\n    $current_test = {}; # reset\n\n    my $fake_config_fn = \"$config_fn/qemu-server/8006.conf\";\n    my $config_raw = file_get_contents($config_fn);\n    my $config = PVE::QemuServer::parse_vm_config($fake_config_fn, $config_raw);\n\n    $current_test->{config} = $config;\n\n    my $description = $config->{description} // '';\n\n    while ($description =~ /^\\h*(.*?)\\h*$/gm) {\n        my $line = $1;\n        next if !$line || $line =~ /^#/;\n        $line =~ s/^\\s+//;\n        $line =~ s/\\s+$//;\n\n        if ($line =~ /^TEST:\\s*(.*)\\s*$/) {\n            $current_test->{description} = \"$1\";\n        } elsif ($line =~ /^QEMU_VERSION:\\s*(.*)\\s*$/) {\n            $current_test->{qemu_version} = \"$1\";\n        } elsif ($line =~ /^HOST_ARCH:\\s*(.*)\\s*$/) {\n            $current_test->{host_arch} = \"$1\";\n        } elsif ($line =~ /^EXPECT_ERROR:\\s*(.*)\\s*$/) {\n            $current_test->{expect_error} = \"$1\";\n        } elsif ($line =~ /^EXPECT_WARN(?:ING)?:\\s*(.*)\\s*$/) {\n            $current_test->{expect_warning} = \"$1\";\n        } elsif ($line =~ /^HW_CAPABILITIES:\\s*(.*)\\s*$/) {\n            $current_test->{hw_capabilities} = \"$1\"; # either a CPU from above hash or raw JSON\n        }\n    }\n\n    $config_fn =~ /([^\\/]+)$/;\n    my $testname = \"$1\";\n    if (my $desc = $current_test->{description}) {\n        $testname = \"'$testname' - $desc\";\n    }\n    $current_test->{testname} = $testname;\n\n    PVE::QemuServer::CPUConfig::initialize_cpu_models();\n}\n\nsub get_test_qemu_version {\n    $current_test->{qemu_version} // $base_env->{real_qemu_version} // '2.12';\n}\n\nmy $qemu_server_module;\n$qemu_server_module = Test::MockModule->new('PVE::QemuServer');\n$qemu_server_module->mock(\n    kvm_user_version => sub {\n        return get_test_qemu_version();\n    },\n    kvm_version => sub {\n        return get_test_qemu_version();\n    },\n    kernel_has_vhost_net => sub {\n        return 1; # TODO: make this per-test configurable?\n    },\n    get_iscsi_initiator_name => sub {\n        return 'iqn.1993-08.org.debian:01:aabbccddeeff';\n    },\n    cleanup_pci_devices => {\n        # do nothing\n    },\n);\n\nmy $qemu_server_ovmf_module = Test::MockModule->new(\"PVE::QemuServer::OVMF\");\n$qemu_server_ovmf_module->mock(\n    file_exists => sub {\n        my ($path) = @_;\n        return 1;\n    },\n    file_get_size => sub {\n        my ($path) = @_;\n        if ($path =~ m/OVMF(32)?_(SEV_)?VARS_4M/) {\n            return 528 * 1024;\n        } elsif ($path =~ m/OVMF_VARS/) {\n            return 128 * 1024;\n        } elsif ($path =~ m/AAVMF_VARS/) {\n            return 64 * 1024 * 1024;\n        } elsif ($path =~ m/RISCV_VIRT_VARS/) {\n            return 32 * 1024 * 1024;\n        } else {\n            die \"unknown ovmf vars image '$path' - implement me\";\n        }\n    },\n);\n\nmy $storage_module = Test::MockModule->new(\"PVE::Storage\");\n$storage_module->mock(\n    activate_volumes => sub {\n        return;\n    },\n    deactivate_volumes => sub {\n        return;\n    },\n    volume_snapshot_info => sub {\n        my ($cfg, $volid) = @_;\n\n        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);\n\n        my $snapshots = {};\n        if ($storeid eq 'localsnapext') {\n            $snapshots = {\n                current => {\n                    file => 'var/lib/vzsnapext/images/8006/vm-8006-disk-0.qcow2',\n                    parent => 'snap2',\n                },\n                snap2 => {\n                    file => '/var/lib/vzsnapext/images/8006/snap2-vm-8006-disk-0.qcow2',\n                    parent => 'snap1',\n                },\n                snap1 => {\n                    file => '/var/lib/vzsnapext/images/8006/snap1-vm-8006-disk-0.qcow2',\n                },\n            };\n        } elsif ($storeid eq 'lvm-store') {\n            $snapshots = {\n                current => {\n                    file => '/dev/veegee/vm-8006-disk-0.qcow2',\n                    parent => 'snap2',\n                },\n                snap2 => {\n                    file => '/dev/veegee/snap2-vm-8006-disk-0.qcow2',\n                    parent => 'snap1',\n                },\n                snap1 => {\n                    file => '/dev/veegee/snap1-vm-8006-disk-0.qcow2',\n                },\n            };\n        }\n        return $snapshots;\n    },\n);\n\nmy $file_stat_module = Test::MockModule->new(\"File::stat\");\n$file_stat_module->mock(\n    stat => sub {\n        my ($path) = @_;\n        my $st = $file_stat_module->original('stat')->('./run_config2command_tests.pl');\n        $st->[2] = 25008 if $path =~ m!/dev/!; # block device\n        return $st;\n    },\n);\n\nmy $zfsplugin_module = Test::MockModule->new(\"PVE::Storage::ZFSPlugin\");\n$zfsplugin_module->mock(\n    zfs_get_lu_name => sub {\n        return \"foobar\";\n    },\n    zfs_get_lun_number => sub {\n        return \"0\";\n    },\n);\n\nmy $rbdplugin_module = Test::MockModule->new(\"PVE::Storage::RBDPlugin\");\n$rbdplugin_module->mock(\n    rbd_volume_config_set => sub {\n        return;\n    },\n);\n\nmy $qemu_server_config;\n$qemu_server_config = Test::MockModule->new('PVE::QemuConfig');\n$qemu_server_config->mock(\n    load_config => sub {\n        my ($class, $vmid, $node) = @_;\n\n        return $current_test->{config};\n    },\n);\n\nmy $qemu_server_helpers;\n$qemu_server_helpers = Test::MockModule->new('PVE::QemuServer::Helpers');\n$qemu_server_helpers->mock(\n    get_host_phys_address_bits => sub {\n        return 46;\n    },\n);\n\nmy $qemu_server_memory;\n$qemu_server_memory = Test::MockModule->new('PVE::QemuServer::Memory');\n$qemu_server_memory->mock(\n    hugepages_chunk_size_supported => sub {\n        return 1;\n    },\n    host_numanode_exists => sub {\n        my ($id) = @_;\n        return 1;\n    },\n);\n\nmy $pve_common_tools;\n$pve_common_tools = Test::MockModule->new('PVE::Tools');\n$pve_common_tools->mock(\n    next_vnc_port => sub {\n        my ($family, $address) = @_;\n\n        return '5900';\n    },\n    next_spice_port => sub {\n        my ($family, $address) = @_;\n\n        return '61000';\n    },\n    getaddrinfo_all => sub {\n        my ($hostname, @opts) = @_;\n        die \"need stable hostname\" if $hostname ne 'localhost';\n        return (\n            {\n                addr => Socket::pack_sockaddr_in(0, Socket::INADDR_LOOPBACK),\n                family => AF_INET, # IPv4\n                protocol => 6,\n                socktype => 1,\n            },\n        );\n    },\n    get_host_arch => sub {\n        return $current_test->{host_arch} // 'x86_64';\n    },\n);\n\nmy $pve_cpuconfig;\n$pve_cpuconfig = Test::MockModule->new('PVE::QemuServer::CPUConfig');\n$pve_cpuconfig->mock(\n    load_custom_model_conf => sub {\n        # mock custom CPU model config\n        return PVE::QemuServer::CPUConfig->parse_config(\n            \"cpu-models.conf\",\n            <<EOF,\n\n# \"qemu64\" is also a default CPU, used here to test that this doesn't matter\ncpu-model: qemu64\n    reported-model athlon\n    flags +aes;+avx;-kvm_pv_unhalt\n    hv-vendor-id testvend\n    phys-bits 40\n\ncpu-model: alldefault\n\nEOF\n        );\n    },\n    get_hw_capabilities => sub {\n        my $hw_capabilities_raw;\n        if (!defined($current_test->{hw_capabilities})) {\n            # default to barebone uncapable HW\n            $hw_capabilities_raw =\n                '{\"amd-sev\":{\"cbitpos\":0,\"reduced-phys-bits\":0,\"sev-support\":false,'\n                . '\"sev-support-es\":false,\"sev-support-snp\":false}}';\n        } elsif (\n            my $cpu_hw_caps = $cpu_hw_capabilities->{ lc($current_test->{hw_capabilities}) }\n        ) {\n            $hw_capabilities_raw = $cpu_hw_caps;\n        } else {\n            $hw_capabilities_raw = $current_test->{hw_capabilities};\n        }\n\n        my $hw_capabilities = decode_json($hw_capabilities_raw);\n        return $hw_capabilities;\n    },\n);\n\nmy $pve_common_network;\n$pve_common_network = Test::MockModule->new('PVE::Network');\n$pve_common_network->mock(\n    read_bridge_mtu => sub {\n        my ($bridge_name) = @_;\n\n        if ($bridge_name eq 'vxlan_bridge') {\n            return 1450;\n        }\n\n        return 1500;\n    },\n);\n\nmy $pve_common_inotify;\n$pve_common_inotify = Test::MockModule->new('PVE::INotify');\n$pve_common_inotify->mock(\n    nodename => sub {\n        return 'localhost';\n    },\n);\n\nmy $pve_common_sysfstools;\n$pve_common_sysfstools = Test::MockModule->new('PVE::SysFSTools');\n$pve_common_sysfstools->mock(\n    lspci => sub {\n        my ($filter, $verbose) = @_;\n\n        return [\n            map { { id => $_ } }\n            grep {\n                !defined($filter)\n                    || (!ref($filter) && $_ =~ m/^(0000:)?\\Q$filter\\E/)\n                    || (ref($filter) eq 'CODE' && $filter->({ id => $_ }))\n            } sort @$pci_devs\n        ];\n    },\n    pci_device_info => sub {\n        my ($path, $noerr) = @_;\n\n        if ($path =~ m/^0000:01:00/) {\n            return {\n                mdev => 1,\n                iommugroup => 1,\n                mdev => 1,\n                vendor => \"0x10de\",\n                device => \"0x2231\",\n            };\n        } elsif ($path =~ m/^0000:07:10/) {\n            return {\n                iommugroup => 2,\n                vendor => \"0x8086\",\n                device => \"0x1520\",\n            };\n        } else {\n            return {};\n        }\n    },\n);\n\nmy $qemu_drive_module;\n$qemu_drive_module = Test::MockModule->new('PVE::QemuServer::Drive');\n$qemu_drive_module->mock(\n    get_cdrom_path => sub {\n        return \"/dev/cdrom\";\n    },\n);\n\nmy $qemu_monitor_module;\n$qemu_monitor_module = Test::MockModule->new('PVE::QemuServer::Monitor');\n$qemu_monitor_module->mock(\n    mon_cmd => sub {\n        my ($vmid, $cmd) = @_;\n\n        die \"invalid vmid: $vmid (expected: $base_env->{vmid})\"\n            if $vmid != $base_env->{vmid};\n\n        if ($cmd eq 'query-version') {\n            my $ver = get_test_qemu_version();\n            $ver =~ m/(\\d+)\\.(\\d+)(?:\\.(\\d+))?/;\n            return {\n                qemu => {\n                    major => $1,\n                    minor => $2,\n                    micro => $3,\n                },\n            };\n        }\n\n        die \"unexpected QMP command: '$cmd'\";\n    },\n);\n\nmy $mapping_usb_module = Test::MockModule->new(\"PVE::Mapping::USB\");\n$mapping_usb_module->mock(\n    config => sub {\n        return $usb_map_config;\n    },\n);\n\nmy $mapping_pci_module = Test::MockModule->new(\"PVE::Mapping::PCI\");\n$mapping_pci_module->mock(\n    config => sub {\n        return $pci_map_config;\n    },\n);\n\nmy $pci_module = Test::MockModule->new(\"PVE::QemuServer::PCI\");\n$pci_module->mock(\n    reserve_pci_usage => sub {\n        die \"reserve_pci_usage should not be called for 'qm showcmd'\\n\";\n    },\n    create_nvidia_device => sub {\n        die \"create_nvidia_device should not be called for 'qm showcmd'\\n\";\n    },\n);\n\nsub diff($a, $b) {\n    return if $a eq $b;\n\n    my ($ra, $wa) = POSIX::pipe();\n    my ($rb, $wb) = POSIX::pipe();\n    my $ha = IO::Handle->new_from_fd($wa, 'w');\n    my $hb = IO::Handle->new_from_fd($wb, 'w');\n\n    open my $diffproc, '-|', 'diff', '-up', \"/proc/self/fd/$ra\", \"/proc/self/fd/$rb\" ## no critic\n        or die \"failed to run program 'diff': $!\";\n    POSIX::close($ra);\n    POSIX::close($rb);\n\n    open my $f1, '<', \\$a;\n    open my $f2, '<', \\$b;\n    my ($line1, $line2);\n    do {\n        $ha->print($line1) if defined($line1 = <$f1>);\n        $hb->print($line2) if defined($line2 = <$f2>);\n    } while (defined($line1 // $line2));\n    close $f1;\n    close $f2;\n    close $ha;\n    close $hb;\n\n    local $/ = undef;\n    my $diff = <$diffproc>;\n    close $diffproc;\n    die \"files differ:\\n$diff\";\n}\n\n$SIG{__WARN__} = sub {\n    my $warning = shift;\n    chomp $warning;\n    if (my $warn_expect = $current_test->{expect_warning}) {\n        if ($warn_expect ne $warning) {\n            fail($current_test->{testname});\n            note(\"warning does not match expected error: '$warning' != '$warn_expect'\");\n        } else {\n            note(\"got expected warning '$warning'\");\n            return;\n        }\n    }\n\n    fail($current_test->{testname});\n    note(\"got unexpected warning '$warning'\");\n};\n\nsub do_test($config_fn) {\n    die \"no such input test config: $config_fn\\n\" if !-f $config_fn;\n\n    parse_test $config_fn;\n\n    my $testname = $current_test->{testname};\n\n    my ($vmid, $storecfg) = $base_env->@{qw(vmid storage_config)};\n\n    my $cmdline = eval { PVE::QemuServer::vm_commandline($storecfg, $vmid) };\n    my $err = $@;\n\n    if (my $err_expect = $current_test->{expect_error}) {\n        if (!$err) {\n            fail(\"$testname\");\n            note(\"did NOT get any error, but expected error: $err_expect\");\n            return;\n        }\n        chomp $err;\n        if ($err ne $err_expect) {\n            fail(\"$testname\");\n            note(\"error does not match expected error: '$err' !~ '$err_expect'\");\n        } else {\n            pass(\"$testname\");\n        }\n        return;\n    } elsif ($err) {\n        fail(\"$testname\");\n        note(\"got unexpected error: $err\");\n        return;\n    }\n\n    # check if QEMU version set correctly and test version_cmp\n    (my $qemu_major = get_test_qemu_version()) =~ s/\\..*$//;\n    die \"runs_at_least_qemu_version returned false, maybe error in version_cmp?\"\n        if !PVE::QemuServer::QMPHelpers::runs_at_least_qemu_version($vmid, $qemu_major);\n\n    $cmdline =~ s/ -/ \\\\\\n  -/g; # same as qm showcmd --pretty\n    $cmdline .= \"\\n\";\n\n    my $cmd_fn = \"$config_fn.cmd\";\n\n    if (-f $cmd_fn) {\n        my $cmdline_expected = file_get_contents($cmd_fn);\n\n        my $cmd_expected = [split /\\s*\\\\?\\n\\s*/, $cmdline_expected];\n        my $cmd = [split /\\s*\\\\?\\n\\s*/, $cmdline];\n\n        # uncomment for easier debugging\n        #file_set_contents(\"$cmd_fn.tmp\", $cmdline);\n\n        my $exp = join(\"\\n\", @$cmd_expected);\n        my $got = join(\"\\n\", @$cmd);\n        eval { diff($exp, $got) };\n        if (my $err = $@) {\n            fail(\"$testname\");\n            note($err);\n        } else {\n            pass(\"$testname\");\n        }\n    } else {\n        file_set_contents($cmd_fn, $cmdline);\n    }\n}\n\nprint \"testing config to command stability\\n\";\n\n# exec tests\nmy $test_target = shift // 'cfg2cmd';\n\nif (-f $test_target) {\n    do_test($test_target);\n} elsif (-d $test_target) {\n    PVE::File::dir_glob_foreach(\n        $test_target,\n        qr/.+\\.conf/,\n        sub {\n            my ($file) = @_;\n\n            do_test(\"${test_target}/${file}\");\n        },\n    );\n} else {\n    die \"test target '$test_target' is neither file nor directory, cannot proceed\\n\";\n}\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_parse_config_tests.pl",
    "content": "#!/usr/bin/perl\n\n# Tests parsing and writing VM configuration files.\n# The parsing part is already covered by the config2command test too, but that only focuses on the\n# main section, not other section types and does not also test parsing in strict mode.\n#\n# If no expected file exists, the input is assumed to be equal to the expected output.\n# If $file.strict.error (respectively $file.non-strict.error) exists, it is assumed to be the\n# expected error when parsing the config in strict (respectively non-strict) mode.\n\nuse strict;\nuse warnings;\n\nuse lib qw(..);\n\nuse File::Path qw(make_path remove_tree);\n\nuse Test::MockModule;\nuse Test::More;\n\nuse PVE::QemuServer;\nuse PVE::Tools;\n\nmy $INPUT_DIR = './parse-config-input';\nmy $OUTPUT_DIR = './parse-config-output';\nmy $EXPECTED_DIR = './parse-config-expected';\n\n# NOTE update when you add/remove tests\nplan tests => 2 * 10;\n\nsub run_tests {\n    my ($strict) = @_;\n\n    PVE::Tools::dir_glob_foreach(\n        './parse-config-input',\n        '.*\\.conf',\n        sub {\n            my ($file) = @_;\n\n            my $strict_mode = $strict ? 'strict' : 'non-strict';\n\n            my $expected_err_file = \"${EXPECTED_DIR}/${file}.${strict_mode}.error\";\n            my $expected_err;\n            $expected_err = PVE::Tools::file_get_contents($expected_err_file)\n                if -f $expected_err_file;\n\n            my $fake_config_fn = \"$file/qemu-server/8006.conf\";\n            my $input_file = \"${INPUT_DIR}/${file}\";\n            my $input = PVE::Tools::file_get_contents($input_file);\n            my $conf =\n                eval { PVE::QemuServer::parse_vm_config($fake_config_fn, $input, $strict); };\n            if (my $err = $@) {\n                if ($expected_err) {\n                    is($err, $expected_err, $file);\n                } else {\n                    note(\"got unexpected error '$err'\");\n                    fail($file);\n                }\n                return;\n            }\n\n            if ($expected_err) {\n                note(\"expected error for strict mode did not occur: '$expected_err'\");\n                fail($file);\n                return;\n            }\n\n            my $output = eval { PVE::QemuServer::write_vm_config($fake_config_fn, $conf); };\n            if (my $err = $@) {\n                note(\"got unexpected error '$err'\");\n                fail($file);\n                return;\n            }\n\n            my $output_file = \"${OUTPUT_DIR}/${file}\";\n            PVE::Tools::file_set_contents($output_file, $output);\n\n            my $expected_file = \"${EXPECTED_DIR}/${file}\";\n            $expected_file = $input_file if !-f $expected_file;\n\n            my $cmd = ['diff', '-u', $expected_file, $output_file];\n            if (system(@$cmd) == 0) {\n                pass($file);\n            } else {\n                fail($file);\n            }\n        },\n    );\n}\n\nmake_path(${OUTPUT_DIR});\nrun_tests(0);\nrun_tests(1);\nremove_tree(${OUTPUT_DIR}) or die \"failed to remove output directory\\n\";\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_pci_addr_checks.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nuse experimental 'smartmatch';\n\nuse lib qw(..);\n\nuse Test::More;\n\nuse PVE::Tools qw(file_get_contents);\nuse PVE::QemuServer::PCI;\n\nmy $qemu_cfg_base_path = \"../usr\";\n\n# not our format but that what QEMU gets passed with '-readconfig'\nsub slurp_qemu_config {\n    my ($fn) = @_;\n\n    my $raw = file_get_contents($fn);\n\n    my $lineno = 0;\n    my $cfg = {};\n    my $group;\n    my $skip_to_next_group;\n    while ($raw =~ /^\\h*(.*?)\\h*$/gm) {\n        my $line = $1;\n        $lineno++;\n        next if !$line || $line =~ /^#/;\n\n        # tried to follow qemu's qemu_config_parse function\n        if ($line =~ /\\[(\\S{1,63}) \"([^\"\\]]{1,63})\"\\]/) {\n            $group = $2;\n            $skip_to_next_group = 0;\n            if ($1 ne 'device') {\n                $group = undef;\n                $skip_to_next_group = 1;\n            }\n        } elsif ($line =~ /\\[([^\\]]{1,63})\\]/) {\n            $group = undef;\n            $skip_to_next_group = 1;\n        } elsif ($group) {\n            if ($line =~ /(\\S{1,63}) = \"([^\\\"]{1,1023})\"/) {\n                my ($k, $v) = ($1, $2);\n                $cfg->{$group}->{$k} = $v;\n            } else {\n                print \"ignoring $fn:$lineno: $line\\n\";\n            }\n        } else {\n            warn \"ignore $fn:$lineno, currently no group\\n\" if !$skip_to_next_group;\n        }\n    }\n\n    return $cfg;\n}\n\nsub extract_qemu_config_addrs {\n    my ($qemu_cfg) = @_;\n\n    my $addr_map = {};\n    for my $k (keys %$qemu_cfg) {\n        my $v = $qemu_cfg->{$k};\n        next if !$v || !defined($v->{bus}) || !defined($v->{addr});\n\n        my $bus = $v->{bus};\n        $bus =~ s/pci\\.//;\n\n        $addr_map->{$k} = { bus => $bus, addr => $v->{addr} };\n    }\n\n    return $addr_map;\n}\n\nprint \"testing PCI(e) address conflicts\\n\";\n\n# exec tests\n\n#FIXME: make cross PCI <-> PCIe check sense at all??\nmy $addr_map = {};\nmy ($fail, $ignored) = (0, 0);\n\nsub check_conflict {\n    my ($id, $what, $ignore_if_same_key) = @_;\n\n    my ($bus, $addr) = $what->@{qw(bus addr)};\n    my $full_addr = \"$bus:$addr\";\n\n    if (defined(my $conflict = $addr_map->{$full_addr})) {\n        if (my @ignores = $what->{conflict_ok}) {\n            if ($conflict ~~ @ignores) {\n                note(\"OK: ignore conflict for '$full_addr' between '$id' and '$conflict'\");\n                $ignored++;\n                return;\n            }\n        }\n        # this allows to read multiple pve-*.cfg qemu configs, and check them\n        # normally their OK if they conflict is on the same key. Else TODO??\n        return if $ignore_if_same_key && $id eq $conflict;\n\n        note(\"ERR: conflict for '$full_addr' between '$id' and '$conflict'\");\n        $fail++;\n    } else {\n        $addr_map->{$full_addr} = $id;\n    }\n}\n\nmy $pci_map = PVE::QemuServer::PCI::get_pci_addr_map();\nwhile (my ($id, $what) = each %$pci_map) {\n    check_conflict($id, $what);\n}\n\nmy $pcie_map = PVE::QemuServer::PCI::get_pcie_addr_map();\nwhile (my ($id, $what) = each %$pcie_map) {\n    check_conflict($id, $what);\n}\n\nmy $pve_qm_cfg = slurp_qemu_config(\"$qemu_cfg_base_path/pve-q35.cfg\");\nmy $pve_qm_cfg_map = extract_qemu_config_addrs($pve_qm_cfg);\nwhile (my ($id, $what) = each %$pve_qm_cfg_map) {\n    check_conflict($id, $what);\n}\n\n# FIXME: restart with clean conflict $addr_map with only get_pci*_addr_map ones?\nmy $pve_qm4_cfg = slurp_qemu_config(\"$qemu_cfg_base_path/pve-q35-4.0.cfg\");\nmy $pve_qm4_cfg_map = extract_qemu_config_addrs($pve_qm4_cfg);\nwhile (my ($id, $what) = each %$pve_qm4_cfg_map) {\n    check_conflict($id, $what, 1);\n}\nmy $pve_qm_usb_cfg = slurp_qemu_config(\"$qemu_cfg_base_path/pve-usb.cfg\");\nmy $pve_qm_usb_cfg_map = extract_qemu_config_addrs($pve_qm_usb_cfg);\nwhile (my ($id, $what) = each %$pve_qm_usb_cfg_map) {\n    check_conflict($id, $what, 1);\n}\n\nif ($fail) {\n    fail(\"PCI(e) address conflict check, ignored: $ignored, conflicts: $fail\");\n} else {\n    pass(\"PCI(e) address conflict check, ignored: $ignored\");\n}\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_pci_reservation_tests.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse lib qw(..);\n\nmy $vmid = 8006;\n\nuse Test::MockModule;\nuse Test::More;\n\nuse PVE::Mapping::PCI;\n\nuse PVE::QemuServer::PCI;\n\nmy $pci_devs = [\n    \"0000:00:43.1\",\n    \"0000:00:f4.0\",\n    \"0000:00:ff.1\",\n    \"0000:0f:f2.0\",\n    \"0000:d0:13.0\",\n    \"0000:d0:15.1\",\n    \"0000:d0:15.2\",\n    \"0000:d0:17.0\",\n    \"0000:f0:42.0\",\n    \"0000:f0:43.0\",\n    \"0000:f0:43.1\",\n    \"1234:f0:43.1\",\n    \"0000:01:00.4\",\n    \"0000:01:00.5\",\n    \"0000:01:00.6\",\n    \"0000:07:10.0\",\n    \"0000:07:10.1\",\n    \"0000:07:10.4\",\n];\n\nmy $pci_map_config = {\n    ids => {\n        someGpu => {\n            type => 'pci',\n            mdev => 1,\n            map => [\n                'node=localhost,path=0000:01:00.4,id=10de:2231,iommugroup=1',\n                'node=localhost,path=0000:01:00.5,id=10de:2231,iommugroup=1',\n                'node=localhost,path=0000:01:00.6,id=10de:2231,iommugroup=1',\n            ],\n        },\n        someNic => {\n            type => 'pci',\n            map => [\n                'node=localhost,path=0000:07:10.0,id=8086:1520,iommugroup=2',\n                'node=localhost,path=0000:07:10.1,id=8086:1520,iommugroup=2',\n                'node=localhost,path=0000:07:10.4,id=8086:1520,iommugroup=2',\n            ],\n        },\n    },\n};\n\nmy $tests = [\n    {\n        name => 'reservation-is-respected',\n        conf => {\n            hostpci0 => 'mapping=someNic',\n            hostpci1 => 'mapping=someGpu,mdev=some-model',\n            hostpci2 => 'mapping=someNic',\n        },\n        expected => {\n            hostpci0 => { ids => [{ id => '0000:07:10.0' }] },\n            hostpci1 => {\n                ids => [\n                    { id => '0000:01:00.4' }, { id => '0000:01:00.5' },\n                    { id => '0000:01:00.6' },\n                ],\n                mdev => 'some-model',\n            },\n            hostpci2 => { ids => [{ id => '0000:07:10.4' }] },\n        },\n    },\n];\n\nplan tests => scalar($tests->@*);\n\nmy $pve_common_inotify;\n$pve_common_inotify = Test::MockModule->new('PVE::INotify');\n$pve_common_inotify->mock(\n    nodename => sub {\n        return 'localhost';\n    },\n);\n\nmy $pve_common_sysfstools;\n$pve_common_sysfstools = Test::MockModule->new('PVE::SysFSTools');\n$pve_common_sysfstools->mock(\n    lspci => sub {\n        my ($filter, $verbose) = @_;\n\n        return [\n            map { { id => $_ } }\n            grep {\n                !defined($filter)\n                    || (!ref($filter) && $_ =~ m/^(0000:)?\\Q$filter\\E/)\n                    || (ref($filter) eq 'CODE' && $filter->({ id => $_ }))\n            } sort @$pci_devs\n        ];\n    },\n    pci_device_info => sub {\n        my ($path, $noerr) = @_;\n\n        if ($path =~ m/^0000:01:00/) {\n            return {\n                mdev => 1,\n                iommugroup => 1,\n                mdev => 1,\n                vendor => \"0x10de\",\n                device => \"0x2231\",\n            };\n        } elsif ($path =~ m/^0000:07:10/) {\n            return {\n                iommugroup => 2,\n                vendor => \"0x8086\",\n                device => \"0x1520\",\n            };\n        } else {\n            return {};\n        }\n    },\n);\n\nmy $mapping_pci_module = Test::MockModule->new(\"PVE::Mapping::PCI\");\n$mapping_pci_module->mock(\n    config => sub {\n        return $pci_map_config;\n    },\n);\n\nmy $pci_module = Test::MockModule->new(\"PVE::QemuServer::PCI\");\n$pci_module->mock(\n    reserve_pci_usage => sub {\n        my ($ids, $vmid, $timeout, $pid, $dryrun) = @_;\n\n        $ids = [$ids] if !ref($ids);\n\n        for my $id (@$ids) {\n            if ($id eq \"0000:07:10.1\") {\n                die \"reserved\";\n            }\n        }\n\n        return undef;\n    },\n    create_nvidia_device => sub {\n        return 1;\n    },\n);\n\nfor my $test ($tests->@*) {\n    my ($name, $conf, $expected) = $test->@{qw(name conf expected)};\n    my $pci_devices;\n    eval {\n        my $devices = PVE::QemuServer::PCI::parse_hostpci_devices($conf);\n        use JSON;\n        $pci_devices = PVE::QemuServer::PCI::choose_hostpci_devices($devices, $vmid);\n    };\n    if (my $err = $@) {\n        is($err, $expected, $name);\n    } elsif ($pci_devices) {\n        is_deeply($pci_devices, $expected, $name);\n    } else {\n        fail($name);\n        note(\"no result\");\n    }\n}\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_qemu_img_convert_tests.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse lib qw(..);\n\nuse Test::More;\nuse Test::MockModule;\n\nuse PVE::QemuServer::QemuImage;\n\nmy $vmid = 8006;\nmy $storage_config = {\n    ids => {\n        local => {\n            content => {\n                images => 1,\n            },\n            path => \"/var/lib/vz\",\n            type => \"dir\",\n            shared => 0,\n        },\n        localsnapext => {\n            content => {\n                images => 1,\n            },\n            path => \"/var/lib/vzsnapext\",\n            type => \"dir\",\n            'snapshot-as-volume-chain' => 1,\n            shared => 0,\n        },\n        btrfs => {\n            content => {\n                images => 1,\n            },\n            path => \"/var/lib/btrfs\",\n            type => \"btrfs\",\n            shared => 0,\n        },\n        \"krbd-store\" => {\n            monhost => \"127.0.0.42,127.0.0.21,::1\",\n            fsid => 'fc4181a6-56eb-4f68-b452-8ba1f381ca2a',\n            content => {\n                images => 1,\n            },\n            type => \"rbd\",\n            krbd => 1,\n            pool => \"apool\",\n            username => \"admin\",\n            shared => 1,\n        },\n        \"rbd-store\" => {\n            monhost => \"127.0.0.42,127.0.0.21,::1\",\n            fsid => 'fc4181a6-56eb-4f68-b452-8ba1f381ca2a',\n            content => {\n                images => 1,\n            },\n            type => \"rbd\",\n            pool => \"cpool\",\n            username => \"admin\",\n            shared => 1,\n        },\n        \"local-lvm\" => {\n            vgname => \"pve\",\n            bwlimit => \"restore=1024\",\n            type => \"lvmthin\",\n            thinpool => \"data\",\n            content => {\n                images => 1,\n            },\n        },\n        \"lvm-store\" => {\n            vgname => \"pve\",\n            type => \"lvm\",\n            content => {\n                images => 1,\n            },\n            'snapshot-as-volume-chain' => 1,\n        },\n        \"zfs-over-iscsi\" => {\n            type => \"zfs\",\n            iscsiprovider => \"LIO\",\n            lio_tpg => \"tpg1\",\n            portal => \"127.0.0.1\",\n            target => \"iqn.2019-10.org.test:foobar\",\n            pool => \"tank\",\n        },\n    },\n};\n\nmy $tests = [\n    {\n        name => 'qcow2raw',\n        parameters =>\n            [\"local:$vmid/vm-$vmid-disk-0.qcow2\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"qcow2\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.qcow2\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"raw2qcow2\",\n        parameters =>\n            [\"local:$vmid/vm-$vmid-disk-0.raw\", \"local:$vmid/vm-$vmid-disk-0.qcow2\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"qcow2\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.qcow2\",\n        ],\n    },\n    {\n        name => \"local2rbd\",\n        parameters =>\n            [\"local:$vmid/vm-$vmid-disk-0.raw\", \"rbd-store:vm-$vmid-disk-0\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"rbd:cpool/vm-$vmid-disk-0:mon_host=127.0.0.42;127.0.0.21;[\\\\:\\\\:1]:auth_supported=none\",\n        ],\n    },\n    {\n        name => \"rbd2local\",\n        parameters =>\n            [\"rbd-store:vm-$vmid-disk-0\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"rbd:cpool/vm-$vmid-disk-0:mon_host=127.0.0.42;127.0.0.21;[\\\\:\\\\:1]:auth_supported=none\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"local2zos\",\n        parameters =>\n            [\"local:$vmid/vm-$vmid-disk-0.raw\", \"zfs-over-iscsi:vm-$vmid-disk-0\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"--target-image-opts\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"file.driver=iscsi,file.transport=tcp,file.initiator-name=foobar,file.portal=127.0.0.1,file.target=iqn.2019-10.org.test:foobar,file.lun=1,driver=raw\",\n        ],\n    },\n    {\n        name => \"zos2local\",\n        parameters =>\n            [\"zfs-over-iscsi:vm-$vmid-disk-0\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"--image-opts\",\n            \"-O\",\n            \"raw\",\n            \"file.driver=iscsi,file.transport=tcp,file.initiator-name=foobar,file.portal=127.0.0.1,file.target=iqn.2019-10.org.test:foobar,file.lun=1,driver=raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"zos2rbd\",\n        parameters =>\n            [\"zfs-over-iscsi:vm-$vmid-disk-0\", \"rbd-store:vm-$vmid-disk-0\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"--image-opts\",\n            \"-O\",\n            \"raw\",\n            \"file.driver=iscsi,file.transport=tcp,file.initiator-name=foobar,file.portal=127.0.0.1,file.target=iqn.2019-10.org.test:foobar,file.lun=1,driver=raw\",\n            \"rbd:cpool/vm-$vmid-disk-0:mon_host=127.0.0.42;127.0.0.21;[\\\\:\\\\:1]:auth_supported=none\",\n        ],\n    },\n    {\n        name => \"rbd2zos\",\n        parameters =>\n            [\"rbd-store:vm-$vmid-disk-0\", \"zfs-over-iscsi:vm-$vmid-disk-0\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"--target-image-opts\",\n            \"rbd:cpool/vm-$vmid-disk-0:mon_host=127.0.0.42;127.0.0.21;[\\\\:\\\\:1]:auth_supported=none\",\n            \"file.driver=iscsi,file.transport=tcp,file.initiator-name=foobar,file.portal=127.0.0.1,file.target=iqn.2019-10.org.test:foobar,file.lun=1,driver=raw\",\n        ],\n    },\n    {\n        name => \"local2lvmthin\",\n        parameters =>\n            [\"local:$vmid/vm-$vmid-disk-0.raw\", \"local-lvm:vm-$vmid-disk-0\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"/dev/pve/vm-$vmid-disk-0\",\n        ],\n    },\n    {\n        name => \"lvmthin2local\",\n        parameters =>\n            [\"local-lvm:vm-$vmid-disk-0\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/dev/pve/vm-$vmid-disk-0\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"zeroinit\",\n        parameters => [\n            \"local-lvm:vm-$vmid-disk-0\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { 'is-zero-initialized' => 1 },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/dev/pve/vm-$vmid-disk-0\",\n            \"zeroinit:/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"notexistingstorage\",\n        parameters =>\n            [\"local-lvm:vm-$vmid-disk-0\", \"not-existing:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => \"storage 'not-existing' does not exist\\n\",\n    },\n    {\n        name => \"vmdkfile\",\n        parameters => [\"./test.vmdk\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"vmdk\",\n            \"-O\",\n            \"raw\",\n            \"./test.vmdk\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"notexistingfile\",\n        parameters => [\"/foo/bar\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => \"source '/foo/bar' is not a valid volid nor path for qemu-img convert\\n\",\n    },\n    {\n        name => \"efidisk\",\n        parameters =>\n            [\"/usr/share/kvm/OVMF_VARS-pure-efi.fd\", \"local:$vmid/vm-$vmid-disk-0.raw\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-O\",\n            \"raw\",\n            \"/usr/share/kvm/OVMF_VARS-pure-efi.fd\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"efi2zos\",\n        parameters =>\n            [\"/usr/share/kvm/OVMF_VARS-pure-efi.fd\", \"zfs-over-iscsi:vm-$vmid-disk-0\", 1024 * 10],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"--target-image-opts\",\n            \"/usr/share/kvm/OVMF_VARS-pure-efi.fd\",\n            \"file.driver=iscsi,file.transport=tcp,file.initiator-name=foobar,file.portal=127.0.0.1,file.target=iqn.2019-10.org.test:foobar,file.lun=1,driver=raw\",\n        ],\n    },\n    {\n        name => \"bwlimit\",\n        parameters => [\n            \"local-lvm:vm-$vmid-disk-0\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { bwlimit => 1024 },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-r\",\n            \"1024K\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/dev/pve/vm-$vmid-disk-0\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"krbdsnapshot\",\n        parameters => [\n            \"krbd-store:vm-$vmid-disk-0\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'foo' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/dev/rbd-pve/fc4181a6-56eb-4f68-b452-8ba1f381ca2a/apool/vm-$vmid-disk-0\\@foo\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"rbdsnapshot\",\n        parameters => [\n            \"rbd-store:vm-$vmid-disk-0\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'foo' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"rbd:cpool/vm-$vmid-disk-0\\@foo:mon_host=127.0.0.42;127.0.0.21;[\\\\:\\\\:1]:auth_supported=none\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"btrfs_raw_snapshots\",\n        parameters => [\n            \"btrfs:$vmid/vm-$vmid-disk-0.raw\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'foo' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/btrfs/images/$vmid/vm-$vmid-disk-0\\@foo/disk.raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"btrfs_qcow2_snapshots\",\n        parameters => [\n            \"btrfs:$vmid/vm-$vmid-disk-0.qcow2\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'snap' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-l\",\n            \"snapshot.name=snap\",\n            \"-f\",\n            \"qcow2\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/btrfs/images/$vmid/vm-$vmid-disk-0.qcow2\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"lvmsnapshot\",\n        parameters => [\n            \"local-lvm:vm-$vmid-disk-0\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'foo' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"-O\",\n            \"raw\",\n            \"/dev/pve/snap_vm-$vmid-disk-0_foo\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"qcow2snapshot\",\n        parameters => [\n            \"local:$vmid/vm-$vmid-disk-0.qcow2\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'snap' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-l\",\n            \"snapshot.name=snap\",\n            \"-f\",\n            \"qcow2\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.qcow2\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"qcow2_external_snapshot\",\n        parameters => [\n            \"localsnapext:$vmid/vm-$vmid-disk-0.qcow2\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'foo' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"qcow2\",\n            \"-O\",\n            \"raw\",\n            \"/var/lib/vzsnapext/images/$vmid/snap-foo-vm-$vmid-disk-0.qcow2\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"lvmqcow2_external_snapshot\",\n        parameters => [\n            \"lvm-store:vm-$vmid-disk-0.qcow2\",\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            1024 * 10,\n            { snapname => 'foo' },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"qcow2\",\n            \"-O\",\n            \"raw\",\n            \"/dev/pve/snap_vm-$vmid-disk-0_foo.qcow2\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n        ],\n    },\n    {\n        name => \"qcow2_external_snapshot_target\",\n        parameters => [\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            \"localsnapext:$vmid/vm-$vmid-disk-target.qcow2\",\n            1024 * 10,\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"--target-image-opts\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"discard-no-unref=true,driver=qcow2,file.driver=file\"\n                . \",file.filename=/var/lib/vzsnapext/images/$vmid/vm-$vmid-disk-target.qcow2\",\n        ],\n    },\n    {\n        name => \"qcow2_external_snapshot_target_zeroinit\",\n        parameters => [\n            \"local:$vmid/vm-$vmid-disk-0.raw\",\n            \"localsnapext:$vmid/vm-$vmid-disk-target.qcow2\",\n            1024 * 10,\n            { 'is-zero-initialized' => 1 },\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"--target-image-opts\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"driver=zeroinit,file.discard-no-unref=true,file.driver=qcow2,file.file.driver=file\"\n                . \",file.file.filename=/var/lib/vzsnapext/images/$vmid/vm-$vmid-disk-target.qcow2\",\n        ],\n    },\n    {\n        name => \"lvmqcow2_external_snapshot_target\",\n        parameters => [\n            \"local:$vmid/vm-$vmid-disk-0.raw\", \"lvm-store:vm-$vmid-disk-target.qcow2\",\n            1024 * 10,\n        ],\n        expected => [\n            \"/usr/bin/qemu-img\",\n            \"convert\",\n            \"-p\",\n            \"-n\",\n            \"-f\",\n            \"raw\",\n            \"--target-image-opts\",\n            \"/var/lib/vz/images/$vmid/vm-$vmid-disk-0.raw\",\n            \"discard-no-unref=true,driver=qcow2,file.driver=host_device\"\n                . \",file.filename=/dev/pve/vm-$vmid-disk-target.qcow2\",\n        ],\n    },\n];\n\nmy $command;\n\nmy $file_stat_module = Test::MockModule->new(\"File::stat\");\n$file_stat_module->mock(\n    stat => sub {\n        my ($path) = @_;\n        my $st = $file_stat_module->original('stat')->('./run_qemu_img_convert_tests.pl');\n        $st->[2] = 25008 if $path =~ m!/dev/!; # block device\n        return $st;\n    },\n);\n\nmy $storage_module = Test::MockModule->new(\"PVE::Storage\");\n$storage_module->mock(\n    config => sub {\n        return $storage_config;\n    },\n    activate_volumes => sub {\n        return 1;\n    },\n    volume_snapshot_info => sub {\n        my ($cfg, $volid) = @_;\n        if (\n            $volid eq \"lvm-store:vm-$vmid-disk-target.qcow2\"\n            || $volid eq \"localsnapext:$vmid/vm-$vmid-disk-target.qcow2\"\n        ) {\n            # target volumes don't have snapshots\n            return {};\n        }\n        die \"mocked volume_snapshot_info called with unexpected volid $volid\\n\";\n    },\n);\n\nmy $lio_module = Test::MockModule->new(\"PVE::Storage::LunCmd::LIO\");\n$lio_module->mock(\n    run_lun_command => sub {\n        return 1;\n    },\n);\n\n# we use the exported run_command so we have to mock it there\nmy $zfsplugin_module = Test::MockModule->new(\"PVE::Storage::ZFSPlugin\");\n$zfsplugin_module->mock(\n    run_command => sub {\n        return 1;\n    },\n);\n\nmy $qemu_server_helpers_module = Test::MockModule->new(\"PVE::QemuServer::Helpers\");\n$qemu_server_helpers_module->mock(\n    get_iscsi_initiator_name => sub {\n        return \"foobar\";\n    },\n);\n\nmy $tools_module = Test::MockModule->new(\"PVE::Tools\");\n$tools_module->mock(\n    run_command => sub {\n        $command = shift;\n    },\n);\n\nforeach my $test (@$tests) {\n    my $name = $test->{name};\n    my $expected = $test->{expected};\n    eval { PVE::QemuServer::QemuImage::convert(@{ $test->{parameters} }) };\n    if (my $err = $@) {\n        is($err, $expected, $name);\n    } elsif (defined($command)) {\n        is_deeply($command, $expected, $name);\n        $command = undef;\n    } else {\n        fail($name);\n        note(\"no command\");\n    }\n}\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_qemu_migrate_tests.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse JSON;\nuse Test::More;\nuse Test::MockModule;\n\nuse PVE::JSONSchema;\nuse PVE::Tools qw(file_set_contents file_get_contents run_command);\n\nmy $QM_LIB_PATH = '..';\nmy $MIGRATE_LIB_PATH = '..';\nmy $RUN_DIR_PATH = './MigrationTest/run/';\n\n# test configuration shared by all tests\n\nmy $replication_config = {\n    'ids' => {\n        '105-0' => {\n            'guest' => '105',\n            'id' => '105-0',\n            'jobnum' => '0',\n            'source' => 'pve0',\n            'target' => 'pve2',\n            'type' => 'local',\n        },\n    },\n    'order' => {\n        '105-0' => 1,\n    },\n};\n\nmy $storage_config = {\n    ids => {\n        local => {\n            content => {\n                images => 1,\n            },\n            path => \"/var/lib/vz\",\n            type => \"dir\",\n            shared => 0,\n        },\n        \"local-lvm\" => {\n            content => {\n                images => 1,\n            },\n            nodes => {\n                pve0 => 1,\n                pve1 => 1,\n            },\n            type => \"lvmthin\",\n            thinpool => \"data\",\n            vgname => \"pve\",\n        },\n        \"local-zfs\" => {\n            content => {\n                images => 1,\n                rootdir => 1,\n            },\n            pool => \"rpool/data\",\n            sparse => 1,\n            type => \"zfspool\",\n        },\n        \"rbd-store\" => {\n            monhost => \"127.0.0.42,127.0.0.21,::1\",\n            fsid => 'fc4181a6-56eb-4f68-b452-8ba1f381ca2a',\n            content => {\n                images => 1,\n            },\n            type => \"rbd\",\n            pool => \"cpool\",\n            username => \"admin\",\n            shared => 1,\n        },\n        \"local-dir\" => {\n            content => {\n                images => 1,\n            },\n            path => \"/some/dir/\",\n            type => \"dir\",\n        },\n        \"other-dir\" => {\n            content => {\n                images => 1,\n            },\n            path => \"/some/other/dir/\",\n            type => \"dir\",\n        },\n        \"zfs-alias-1\" => {\n            content => {\n                images => 1,\n                rootdir => 1,\n            },\n            pool => \"aliaspool\",\n            sparse => 1,\n            type => \"zfspool\",\n        },\n        \"zfs-alias-2\" => {\n            content => {\n                images => 1,\n                rootdir => 1,\n            },\n            pool => \"aliaspool\",\n            sparse => 1,\n            type => \"zfspool\",\n        },\n    },\n};\n\nmy $vm_configs = {\n    105 => {\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'ide0' => 'local-zfs:vm-105-disk-1,size=103M',\n        'ide2' => 'none,media=cdrom',\n        'memory' => 512,\n        'name' => 'Copy-of-VM-newapache',\n        'net0' => 'virtio=4A:A3:E4:4C:CF:F0,bridge=vmbr0,firewall=1',\n        'numa' => 0,\n        'ostype' => 'l26',\n        'parent' => 'ohsnap',\n        'pending' => {},\n        'scsi0' => 'local-zfs:vm-105-disk-0,size=4G',\n        'scsihw' => 'virtio-scsi-pci',\n        'smbios1' => 'uuid=1ddfe18b-77e0-47f6-a4bd-f1761bf6d763',\n        'snapshots' => {\n            'ohsnap' => {\n                'bootdisk' => 'scsi0',\n                'cores' => 1,\n                'ide2' => 'none,media=cdrom',\n                'memory' => 512,\n                'name' => 'Copy-of-VM-newapache',\n                'net0' => 'virtio=4A:A3:E4:4C:CF:F0,bridge=vmbr0,firewall=1',\n                'numa' => 0,\n                'ostype' => 'l26',\n                'scsi0' => 'local-zfs:vm-105-disk-0,size=4G',\n                'scsihw' => 'virtio-scsi-pci',\n                'smbios1' => 'uuid=1ddfe18b-77e0-47f6-a4bd-f1761bf6d763',\n                'snaptime' => 1580976924,\n                'sockets' => 1,\n                'startup' => 'order=2',\n                'vmgenid' => '4eb1d535-9381-4ddc-a8aa-af50c4d9177b',\n            },\n        },\n        'sockets' => 1,\n        'startup' => 'order=2',\n        'vmgenid' => '4eb1d535-9381-4ddc-a8aa-af50c4d9177b',\n    },\n    111 => {\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'ide0' => 'local-lvm:vm-111-disk-0,size=4096M',\n        'ide2' => 'none,media=cdrom',\n        'memory' => 512,\n        'name' => 'pending-test',\n        'net0' => 'virtio=4A:A3:E4:4C:CF:F0,bridge=vmbr0,firewall=1',\n        'numa' => 0,\n        'ostype' => 'l26',\n        'pending' => {\n            'scsi0' => 'local-zfs:vm-111-disk-0,size=103M',\n        },\n        'scsihw' => 'virtio-scsi-pci',\n        'snapshots' => {},\n        'smbios1' => 'uuid=5ad71d4d-8f73-4377-853e-2d22c10c96a5',\n        'sockets' => 1,\n        'vmgenid' => '2c00c030-0b5b-4988-a371-6ab259893f22',\n    },\n    123 => {\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'scsi0' => 'zfs-alias-1:vm-123-disk-0,size=4096M',\n        'scsi1' => 'zfs-alias-2:vm-123-disk-0,size=4096M',\n        'ide2' => 'none,media=cdrom',\n        'memory' => 512,\n        'name' => 'alias-test',\n        'net0' => 'virtio=4A:A3:E4:4C:CF:F0,bridge=vmbr0,firewall=1',\n        'numa' => 0,\n        'ostype' => 'l26',\n        'pending' => {},\n        'scsihw' => 'virtio-scsi-pci',\n        'snapshots' => {},\n        'smbios1' => 'uuid=5ad71d4d-8f73-4377-853e-2d22c10c96a5',\n        'sockets' => 1,\n        'vmgenid' => '2c00c030-0b5b-4988-a371-6ab259893f22',\n    },\n    149 => {\n        'agent' => '0',\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'hotplug' => 'disk,network,usb,memory,cpu',\n        'ide2' => 'none,media=cdrom',\n        'memory' => 4096,\n        'name' => 'asdf',\n        'net0' => 'virtio=52:5D:7E:62:85:97,bridge=vmbr1',\n        'numa' => 1,\n        'ostype' => 'l26',\n        'scsi0' => 'local-lvm:vm-149-disk-0,format=raw,size=4G',\n        'scsi1' => 'local-dir:149/vm-149-disk-0.qcow2,format=qcow2,size=1G',\n        'scsihw' => 'virtio-scsi-pci',\n        'snapshots' => {},\n        'smbios1' => 'uuid=e980bd43-a405-42e2-b5f4-31efe6517460',\n        'sockets' => 1,\n        'startup' => 'order=2',\n        'vmgenid' => '36c6c50c-6ef5-4adc-9b6f-6ba9c8071db0',\n    },\n    341 => {\n        'arch' => 'aarch64',\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'efidisk0' => 'local-lvm:vm-341-disk-0',\n        'ide2' => 'none,media=cdrom',\n        'ipconfig0' => 'ip=103.214.69.10/25,gw=103.214.69.1',\n        'memory' => 4096,\n        'name' => 'VM1033',\n        'net0' => 'virtio=4E:F1:82:6D:D7:4B,bridge=vmbr0,firewall=1,rate=10',\n        'numa' => 0,\n        'ostype' => 'l26',\n        'scsi0' => 'rbd-store:vm-341-disk-0,size=1G',\n        'scsihw' => 'virtio-scsi-pci',\n        'snapshots' => {},\n        'smbios1' => 'uuid=e01e4c73-46f1-47c8-af79-288fdf6b7462',\n        'sockets' => 2,\n        'vmgenid' => 'af47c000-eb0c-48e8-8991-ca4593cd6916',\n    },\n    1033 => {\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'ide0' => 'rbd-store:vm-1033-cloudinit,media=cdrom,size=4M',\n        'ide2' => 'none,media=cdrom',\n        'ipconfig0' => 'ip=103.214.69.10/25,gw=103.214.69.1',\n        'memory' => 4096,\n        'name' => 'VM1033',\n        'net0' => 'virtio=4E:F1:82:6D:D7:4B,bridge=vmbr0,firewall=1,rate=10',\n        'numa' => 0,\n        'ostype' => 'l26',\n        'scsi0' => 'rbd-store:vm-1033-disk-1,size=1G',\n        'scsihw' => 'virtio-scsi-pci',\n        'snapshots' => {},\n        'smbios1' => 'uuid=e01e4c73-46f1-47c8-af79-288fdf6b7462',\n        'sockets' => 2,\n        'vmgenid' => 'af47c000-eb0c-48e8-8991-ca4593cd6916',\n    },\n    4567 => {\n        'bootdisk' => 'scsi0',\n        'cores' => 1,\n        'ide2' => 'none,media=cdrom',\n        'memory' => 512,\n        'name' => 'snapme',\n        'net0' => 'virtio=A6:D1:F1:EB:7B:C2,bridge=vmbr0,firewall=1',\n        'numa' => 0,\n        'ostype' => 'l26',\n        'parent' => 'snap1',\n        'pending' => {},\n        'scsi0' => 'local-dir:4567/vm-4567-disk-0.qcow2,size=4G',\n        'scsihw' => 'virtio-scsi-pci',\n        'smbios1' => 'uuid=2925fdec-a066-4228-b46b-eef8662f5e74',\n        'snapshots' => {\n            'snap1' => {\n                'bootdisk' => 'scsi0',\n                'cores' => 1,\n                'ide2' => 'none,media=cdrom',\n                'memory' => 512,\n                'name' => 'snapme',\n                'net0' => 'virtio=A6:D1:F1:EB:7B:C2,bridge=vmbr0,firewall=1',\n                'numa' => 0,\n                'ostype' => 'l26',\n                'runningcpu' => 'kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep',\n                'runningmachine' => 'pc-i440fx-10.0+pve0',\n                'scsi0' => 'local-dir:4567/vm-4567-disk-0.qcow2,size=4G',\n                'scsihw' => 'virtio-scsi-pci',\n                'smbios1' => 'uuid=2925fdec-a066-4228-b46b-eef8662f5e74',\n                'snaptime' => 1595928799,\n                'sockets' => 1,\n                'startup' => 'order=2',\n                'vmgenid' => '932b227a-8a39-4ede-955a-dbd4bc4385ed',\n                'vmstate' => 'local-dir:4567/vm-4567-state-snap1.raw',\n            },\n            'snap2' => {\n                'bootdisk' => 'scsi0',\n                'cores' => 1,\n                'ide2' => 'none,media=cdrom',\n                'memory' => 512,\n                'name' => 'snapme',\n                'net0' => 'virtio=A6:D1:F1:EB:7B:C2,bridge=vmbr0,firewall=1',\n                'numa' => 0,\n                'ostype' => 'l26',\n                'parent' => 'snap1',\n                'runningcpu' => 'kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep',\n                'runningmachine' => 'pc-i440fx-10.0+pve0',\n                'scsi0' => 'local-dir:4567/vm-4567-disk-0.qcow2,size=4G',\n                'scsi1' => 'local-zfs:vm-4567-disk-0,size=1G',\n                'scsihw' => 'virtio-scsi-pci',\n                'smbios1' => 'uuid=2925fdec-a066-4228-b46b-eef8662f5e74',\n                'snaptime' => 1595928871,\n                'sockets' => 1,\n                'startup' => 'order=2',\n                'vmgenid' => '932b227a-8a39-4ede-955a-dbd4bc4385ed',\n                'vmstate' => 'local-dir:4567/vm-4567-state-snap2.raw',\n            },\n        },\n        'sockets' => 1,\n        'startup' => 'order=2',\n        'unused0' => 'local-zfs:vm-4567-disk-0',\n        'vmgenid' => 'e698e60c-9278-4dd9-941f-416075383f2a',\n    },\n};\n\nmy $source_vdisks = {\n    'local-dir' => [\n        {\n            'ctime' => 1589439681,\n            'format' => 'qcow2',\n            'parent' => undef,\n            'size' => 1073741824,\n            'used' => 335872,\n            'vmid' => '149',\n            'volid' => 'local-dir:149/vm-149-disk-0.qcow2',\n        },\n        {\n            'ctime' => 1595928898,\n            'format' => 'qcow2',\n            'parent' => undef,\n            'size' => 4294967296,\n            'used' => 1811664896,\n            'vmid' => '4567',\n            'volid' => 'local-dir:4567/vm-4567-disk-0.qcow2',\n        },\n        {\n            'ctime' => 1595928800,\n            'format' => 'raw',\n            'parent' => undef,\n            'size' => 274666496,\n            'used' => 274669568,\n            'vmid' => '4567',\n            'volid' => 'local-dir:4567/vm-4567-state-snap1.raw',\n        },\n        {\n            'ctime' => 1595928872,\n            'format' => 'raw',\n            'parent' => undef,\n            'size' => 273258496,\n            'used' => 273260544,\n            'vmid' => '4567',\n            'volid' => 'local-dir:4567/vm-4567-state-snap2.raw',\n        },\n    ],\n    'local-lvm' => [\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 4294967296,\n            'vmid' => '149',\n            'volid' => 'local-lvm:vm-149-disk-0',\n        },\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 4194304,\n            'vmid' => '341',\n            'volid' => 'local-lvm:vm-341-disk-0',\n        },\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 4294967296,\n            'vmid' => '111',\n            'volid' => 'local-lvm:vm-111-disk-0',\n        },\n    ],\n    'local-zfs' => [\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 4294967296,\n            'vmid' => '105',\n            'volid' => 'local-zfs:vm-105-disk-0',\n        },\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 108003328,\n            'vmid' => '105',\n            'volid' => 'local-zfs:vm-105-disk-1',\n        },\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 108003328,\n            'vmid' => '111',\n            'volid' => 'local-zfs:vm-111-disk-0',\n        },\n        {\n            'format' => 'raw',\n            'name' => 'vm-4567-disk-0',\n            'parent' => undef,\n            'size' => 1073741824,\n            'vmid' => '4567',\n            'volid' => 'local-zfs:vm-4567-disk-0',\n        },\n    ],\n    'rbd-store' => [\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 1073741824,\n            'vmid' => '1033',\n            'volid' => 'rbd-store:vm-1033-disk-1',\n        },\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 1073741824,\n            'vmid' => '1033',\n            'volid' => 'rbd-store:vm-1033-cloudinit',\n        },\n    ],\n    'zfs-alias-1' => [\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 4294967296,\n            'vmid' => '123',\n            'volid' => 'zfs-alias-1:vm-123-disk-0',\n        },\n    ],\n    'zfs-alias-2' => [\n        {\n            'ctime' => '1589277334',\n            'format' => 'raw',\n            'size' => 4294967296,\n            'vmid' => '123',\n            'volid' => 'zfs-alias-2:vm-123-disk-0',\n        },\n    ],\n};\n\nmy $default_expected_calls_online = {\n    move_config_to_node => 1,\n    ssh_qm_start => 1,\n    vm_stop => 1,\n};\n\nmy $default_expected_calls_offline = {\n    move_config_to_node => 1,\n};\n\nmy $replicated_expected_calls_online = {\n    %{$default_expected_calls_online},\n    transfer_replication_state => 1,\n    switch_replication_job_target => 1,\n};\n\nmy $replicated_expected_calls_offline = {\n    %{$default_expected_calls_offline},\n    transfer_replication_state => 1,\n    switch_replication_job_target => 1,\n};\n\n# helpers\n\nsub get_patched_config {\n    my ($vmid, $patch) = @_;\n\n    my $new_config = { %{ $vm_configs->{$vmid} } };\n    patch_config($new_config, $patch) if defined($patch);\n\n    return $new_config;\n}\n\nsub patch_config {\n    my ($config, $patch) = @_;\n\n    foreach my $key (keys %{$patch}) {\n        if ($key eq 'snapshots' && defined($patch->{$key})) {\n            my $new_snapshot_configs = {};\n            foreach my $snap (keys %{ $patch->{snapshots} }) {\n                my $new_snapshot_config = { %{ $config->{snapshots}->{$snap} } };\n                patch_config($new_snapshot_config, $patch->{snapshots}->{$snap});\n                $new_snapshot_configs->{$snap} = $new_snapshot_config;\n            }\n            $config->{snapshots} = $new_snapshot_configs;\n        } elsif (defined($patch->{$key})) {\n            $config->{$key} = $patch->{$key};\n        } else { # use undef value for deletion\n            delete $config->{$key};\n        }\n    }\n}\n\nsub local_volids_for_vm {\n    my ($vmid) = @_;\n\n    my $res = {};\n    foreach my $storeid (keys %{$source_vdisks}) {\n        next if $storage_config->{ids}->{$storeid}->{shared};\n        $res = {\n            %{$res},\n            map { $_->{vmid} eq $vmid ? ($_->{volid} => 1) : () }\n                @{ $source_vdisks->{$storeid} },\n        };\n    }\n    return $res;\n}\n\nmy $tests = [\n    # each test consists of the following:\n    # name           - unique name for the test which also serves as a dir name.\n    #                  NOTE: gets passed to make, so don't use whitespace or slash\n    #                        and adapt buildsys (regex) on code structure changes\n    # target         - hostname of target node\n    # vmid           - ID of the VM to migrate\n    # opts           - options for the migrate() call\n    # target_volids  - hash of volids on the target at the beginning\n    # vm_status      - hash with running, runningmachine and optionally runningcpu\n    # expected_calls - hash whose keys are calls which are required\n    #                  to be made if the migration gets far enough\n    # expect_die     - expect the migration call to fail, and an error message\n    #                  matching the specified text in the log\n    # expected       - hash consisting of:\n    #                  source_volids    - hash of volids expected on the source\n    #                  target_volids    - hash of volids expected on the target\n    #                  vm_config        - vm configuration hash\n    #                  vm_status        - hash with running, runningmachine and optionally runningcpu\n    {\n        # NOTE get_efivars_size is mocked and returns 128K\n        name => '341_running_efidisk_targetstorage_dir',\n        target => 'pve1',\n        vmid => 341,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n            targetstorage => 'local-dir',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-dir:341/vm-341-disk-10.raw' => 1,\n            },\n            vm_config => get_patched_config(\n                341,\n                {\n                    efidisk0 => 'local-dir:341/vm-341-disk-10.raw,format=raw,size=128K',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        # NOTE get_efivars_size is mocked and returns 128K\n        name => '341_running_efidisk',\n        target => 'pve1',\n        vmid => 341,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-lvm:vm-341-disk-10' => 1,\n            },\n            vm_config => get_patched_config(\n                341,\n                {\n                    efidisk0 => 'local-lvm:vm-341-disk-10,format=raw,size=128K',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_vdisk_alloc_and_pvesm_free_fail',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        fail_config => {\n            vdisk_alloc => 'local-dir:149/vm-149-disk-11.qcow2',\n            pvesm_free => 'local-lvm:vm-149-disk-10',\n        },\n        expected_calls => {},\n        expect_die => \"remote command failed with exit code\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {\n                'local-lvm:vm-149-disk-10' => 1,\n            },\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_vdisk_alloc_fail',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        fail_config => {\n            vdisk_alloc => 'local-lvm:vm-149-disk-10',\n        },\n        expected_calls => {},\n        expect_die => \"remote command failed with exit code\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_vdisk_free_fail',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            'with-local-disks' => 1,\n        },\n        fail_config => {\n            'vdisk_free' => 'local-lvm:vm-149-disk-0',\n        },\n        expected_calls => $default_expected_calls_offline,\n        expect_die => \"vdisk_free 'local-lvm:vm-149-disk-0' error\",\n        expected => {\n            source_volids => {\n                'local-lvm:vm-149-disk-0' => 1,\n            },\n            target_volids => local_volids_for_vm(149),\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_replicated_run_replication_fail',\n        target => 'pve2',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        target_volids => local_volids_for_vm(105),\n        fail_config => {\n            run_replication => 1,\n        },\n        expected_calls => {},\n        expect_die => 'run_replication error',\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => local_volids_for_vm(105),\n            vm_config => $vm_configs->{105},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '1033_running_query_migrate_fail',\n        target => 'pve2',\n        vmid => 1033,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n        },\n        fail_config => {\n            'query-migrate' => 1,\n        },\n        expected_calls => {},\n        expect_die => 'online migrate failure - aborting',\n        expected => {\n            source_volids => {},\n            target_volids => {},\n            vm_config => $vm_configs->{1033},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '4567_targetstorage_dirotherdir',\n        target => 'pve1',\n        vmid => 4567,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            targetstorage => 'local-dir:other-dir,local-zfs:local-zfs',\n        },\n        storage_migrate_map => {\n            'local-dir:4567/vm-4567-disk-0.qcow2' => '4567/vm-4567-disk-0.qcow2',\n            'local-dir:4567/vm-4567-state-snap1.raw' => '4567/vm-4567-state-snap1.raw',\n            'local-dir:4567/vm-4567-state-snap2.raw' => '4567/vm-4567-state-snap2.raw',\n        },\n        expected_calls => $default_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'other-dir:4567/vm-4567-disk-0.qcow2' => 1,\n                'other-dir:4567/vm-4567-state-snap1.raw' => 1,\n                'other-dir:4567/vm-4567-state-snap2.raw' => 1,\n                'local-zfs:vm-4567-disk-0' => 1,\n            },\n            vm_config => get_patched_config(\n                4567,\n                {\n                    'scsi0' => 'other-dir:4567/vm-4567-disk-0.qcow2,size=4G',\n                    snapshots => {\n                        snap1 => {\n                            'scsi0' => 'other-dir:4567/vm-4567-disk-0.qcow2,size=4G',\n                            'vmstate' => 'other-dir:4567/vm-4567-state-snap1.raw',\n                        },\n                        snap2 => {\n                            'scsi0' => 'other-dir:4567/vm-4567-disk-0.qcow2,size=4G',\n                            'scsi1' => 'local-zfs:vm-4567-disk-0,size=1G',\n                            'vmstate' => 'other-dir:4567/vm-4567-state-snap2.raw',\n                        },\n                    },\n                },\n            ),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '4567_running',\n        target => 'pve1',\n        vmid => 4567,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-i440fx-10.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        expected_calls => {},\n        expect_die => 'online storage migration not possible if non-replicated snapshot exists',\n        expected => {\n            source_volids => local_volids_for_vm(4567),\n            target_volids => {},\n            vm_config => $vm_configs->{4567},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-i440fx-10.0+pve0',\n            },\n        },\n    },\n    {\n        name => '4567_offline',\n        target => 'pve1',\n        vmid => 4567,\n        vm_status => {\n            running => 0,\n        },\n        expected_calls => $default_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => local_volids_for_vm(4567),\n            vm_config => $vm_configs->{4567},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '149_running_orphaned_disk_targetstorage_zfs',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n            targetstorage => 'local-zfs',\n        },\n        config_patch => {\n            scsi1 => undef,\n        },\n        storage_migrate_map => {\n            'local-dir:149/vm-149-disk-0.qcow2' => 'vm-149-disk-0',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {\n                'local-dir:149/vm-149-disk-0.qcow2' => 1,\n            },\n            target_volids => {\n                'local-zfs:vm-149-disk-10' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-zfs:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => undef,\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_orphaned_disk',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        config_patch => {\n            scsi1 => undef,\n        },\n        storage_migrate_map => {\n            'local-dir:149/vm-149-disk-0.qcow2' => '149/vm-149-disk-0.qcow2',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {\n                'local-dir:149/vm-149-disk-0.qcow2' => 1,\n            },\n            target_volids => {\n                'local-lvm:vm-149-disk-10' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-lvm:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => undef,\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        # FIXME: This test is not (yet) a realistic situation, because\n        # storage_migrate currently never changes the format (AFAICT)\n        # But if such migrations become possible, we need to either update\n        # the 'format' property or simply remove it for drives migrated\n        # with storage_migrate (the property is optional, so it shouldn't be a problem)\n        name => '149_targetstorage_map_lvmzfs_defaultlvm',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            targetstorage => 'local-lvm:local-zfs,local-lvm',\n        },\n        storage_migrate_map => {\n            'local-lvm:vm-149-disk-0' => 'vm-149-disk-0',\n            'local-dir:149/vm-149-disk-0.qcow2' => 'vm-149-disk-0',\n        },\n        expected_calls => $default_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-zfs:vm-149-disk-0' => 1,\n                'local-lvm:vm-149-disk-0' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-zfs:vm-149-disk-0,format=raw,size=4G',\n                    scsi1 => 'local-lvm:vm-149-disk-0,format=qcow2,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        # FIXME same as for the previous test\n        name => '149_targetstorage_map_dirzfs_lvmdir',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n            targetstorage => 'local-dir:local-zfs,local-lvm:local-dir',\n        },\n        storage_migrate_map => {\n            'local-lvm:vm-149-disk-0' => '149/vm-149-disk-0.raw',\n            'local-dir:149/vm-149-disk-0.qcow2' => 'vm-149-disk-0',\n        },\n        expected_calls => $default_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-dir:149/vm-149-disk-0.raw' => 1,\n                'local-zfs:vm-149-disk-0' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-dir:149/vm-149-disk-0.raw,format=raw,size=4G',\n                    scsi1 => 'local-zfs:vm-149-disk-0,format=qcow2,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '149_running_targetstorage_map_lvmzfs_defaultlvm',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n            targetstorage => 'local-lvm:local-zfs,local-lvm',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-zfs:vm-149-disk-10' => 1,\n                'local-lvm:vm-149-disk-11' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-zfs:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => 'local-lvm:vm-149-disk-11,format=raw,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_targetstorage_map_lvmzfs_dirdir',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n            targetstorage => 'local-lvm:local-zfs,local-dir:local-dir',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-zfs:vm-149-disk-10' => 1,\n                'local-dir:149/vm-149-disk-11.qcow2' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-zfs:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => 'local-dir:149/vm-149-disk-11.qcow2,format=qcow2,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_targetstorage_zfs',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n            targetstorage => 'local-zfs',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-zfs:vm-149-disk-10' => 1,\n                'local-zfs:vm-149-disk-11' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-zfs:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => 'local-zfs:vm-149-disk-11,format=raw,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_wrong_size',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        config_patch => {\n            scsi0 => 'local-lvm:vm-149-disk-0,size=123T',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-lvm:vm-149-disk-10' => 1,\n                'local-dir:149/vm-149-disk-11.qcow2' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-lvm:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => 'local-dir:149/vm-149-disk-11.qcow2,format=qcow2,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_missing_size',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        config_patch => {\n            scsi0 => 'local-lvm:vm-149-disk-0',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-lvm:vm-149-disk-10' => 1,\n                'local-dir:149/vm-149-disk-11.qcow2' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-lvm:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => 'local-dir:149/vm-149-disk-11.qcow2,format=qcow2,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '105_local_device_shared',\n        target => 'pve1',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            ide2 => '/dev/sde,shared=1',\n        },\n        expected_calls => $default_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => local_volids_for_vm(105),\n            vm_config => get_patched_config(105, {\n                    ide2 => '/dev/sde,shared=1',\n            }),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_local_device_in_snapshot',\n        target => 'pve1',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            snapshots => {\n                ohsnap => {\n                    ide2 => '/dev/sde',\n                },\n            },\n        },\n        expected_calls => {},\n        expect_die => \"can't migrate local disk '/dev/sde': local file/device\",\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => {},\n            vm_config => get_patched_config(105, {\n                    snapshots => {\n                        ohsnap => {\n                            ide2 => '/dev/sde',\n                        },\n                    },\n            }),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_local_device',\n        target => 'pve1',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            ide2 => '/dev/sde',\n        },\n        expected_calls => {},\n        expect_die => \"can't migrate local disk '/dev/sde': local file/device\",\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => {},\n            vm_config => get_patched_config(105, {\n                    ide2 => '/dev/sde',\n            }),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_cdrom_in_snapshot',\n        target => 'pve1',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            snapshots => {\n                ohsnap => {\n                    ide2 => 'cdrom,media=cdrom',\n                },\n            },\n        },\n        expected_calls => {},\n        expect_die => \"can't migrate local cdrom drive (referenced in snapshot - ohsnap\",\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => {},\n            vm_config => get_patched_config(\n                105,\n                {\n                    snapshots => {\n                        ohsnap => {\n                            ide2 => 'cdrom,media=cdrom',\n                        },\n                    },\n                },\n            ),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_cdrom',\n        target => 'pve1',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            ide2 => 'cdrom,media=cdrom',\n        },\n        expected_calls => {},\n        expect_die => \"can't migrate local cdrom drive\",\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => {},\n            vm_config => get_patched_config(105, {\n                    ide2 => 'cdrom,media=cdrom',\n            }),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '149_running_missing_option_withlocaldisks',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n        },\n        expected_calls => {},\n        expect_die => \"can't live migrate attached local disks without with-local-disks option\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_missing_option_online',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            'with-local-disks' => 1,\n        },\n        expected_calls => {},\n        expect_die => \"can't migrate running VM without --online\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '1033_running_customcpu',\n        target => 'pve1',\n        vmid => 1033,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n            runningcpu => 'host,+kvm_pv_eoi,+kvm_pv_unhalt',\n        },\n        opts => {\n            online => 1,\n        },\n        config_patch => {\n            cpu => 'custom-mycpu',\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {},\n            vm_config => get_patched_config(1033, {\n                    cpu => 'custom-mycpu',\n            }),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n                runningcpu => 'host,+kvm_pv_eoi,+kvm_pv_unhalt',\n            },\n        },\n    },\n    {\n        name => '105_replicated_to_non_replication_target',\n        target => 'pve1',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        target_volids => {},\n        expected_calls => $replicated_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => local_volids_for_vm(105),\n            vm_config => $vm_configs->{105},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_running_replicated',\n        target => 'pve2',\n        vmid => 105,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-i440fx-10.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        target_volids => local_volids_for_vm(105),\n        expected_calls => {\n            %{$replicated_expected_calls_online},\n            'block-dirty-bitmap-add-drive-scsi0' => 1,\n            'block-dirty-bitmap-add-drive-ide0' => 1,\n        },\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => local_volids_for_vm(105),\n            vm_config => $vm_configs->{105},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-i440fx-10.0+pve0',\n            },\n        },\n    },\n    {\n        name => '105_replicated',\n        target => 'pve2',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        target_volids => local_volids_for_vm(105),\n        expected_calls => $replicated_expected_calls_offline,\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => local_volids_for_vm(105),\n            vm_config => $vm_configs->{105},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '105_running_replicated_without_snapshot',\n        target => 'pve2',\n        vmid => 105,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-i440fx-10.0+pve0',\n        },\n        config_patch => {\n            snapshots => undef,\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        target_volids => local_volids_for_vm(105),\n        expected_calls => {\n            %{$replicated_expected_calls_online},\n            'block-dirty-bitmap-add-drive-scsi0' => 1,\n            'block-dirty-bitmap-add-drive-ide0' => 1,\n        },\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => local_volids_for_vm(105),\n            vm_config => get_patched_config(105, {\n                    snapshots => {},\n            }),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-i440fx-10.0+pve0',\n            },\n        },\n    },\n    {\n        name => '105_replicated_without_snapshot',\n        target => 'pve2',\n        vmid => 105,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            snapshots => undef,\n        },\n        opts => {\n            online => 1,\n        },\n        target_volids => local_volids_for_vm(105),\n        expected_calls => $replicated_expected_calls_offline,\n        expected => {\n            source_volids => local_volids_for_vm(105),\n            target_volids => local_volids_for_vm(105),\n            vm_config => get_patched_config(105, {\n                    snapshots => {},\n            }),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '1033_running',\n        target => 'pve2',\n        vmid => 1033,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {},\n            vm_config => $vm_configs->{1033},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_locked',\n        target => 'pve2',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        config_patch => {\n            lock => 'locked',\n        },\n        expected_calls => {},\n        expect_die => \"VM is locked\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => get_patched_config(149, {\n                    lock => 'locked',\n            }),\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '149_storage_not_available',\n        target => 'pve2',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        expected_calls => {},\n        expect_die => \"storage 'local-lvm' is not available on node 'pve2'\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '149_running',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-lvm:vm-149-disk-10' => 1,\n                'local-dir:149/vm-149-disk-11.qcow2' => 1,\n            },\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi0 => 'local-lvm:vm-149-disk-10,format=raw,size=4G',\n                    scsi1 => 'local-dir:149/vm-149-disk-11.qcow2,format=qcow2,size=1G',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_drive_mirror_fail',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        expected_calls => {},\n        expect_die => \"qemu_drive_mirror 'scsi1' error\",\n        fail_config => {\n            'qemu_drive_mirror' => 'scsi1',\n        },\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_running_unused_block_job_cancel_fail',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        config_patch => {\n            scsi1 => undef,\n            unused0 => 'local-dir:149/vm-149-disk-0.qcow2',\n        },\n        expected_calls => {},\n        expect_die => \"block_job_monitor 'cancel' error\",\n        # note that 'cancel' is also used to finish and that's what this test is about\n        fail_config => {\n            'block_job_monitor' => 'cancel',\n        },\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => get_patched_config(\n                149,\n                {\n                    scsi1 => undef,\n                    unused0 => 'local-dir:149/vm-149-disk-0.qcow2',\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '149_offline',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            'with-local-disks' => 1,\n        },\n        expected_calls => $default_expected_calls_offline,\n        expected => {\n            source_volids => {},\n            target_volids => local_volids_for_vm(149),\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '149_storage_migrate_fail',\n        target => 'pve1',\n        vmid => 149,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            'with-local-disks' => 1,\n        },\n        fail_config => {\n            'storage_migrate' => 'local-lvm:vm-149-disk-0',\n        },\n        expected_calls => {},\n        expect_die => \"storage_migrate 'local-lvm:vm-149-disk-0' error\",\n        expected => {\n            source_volids => local_volids_for_vm(149),\n            target_volids => {},\n            vm_config => $vm_configs->{149},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n    {\n        name => '111_running_pending',\n        target => 'pve1',\n        vmid => 111,\n        vm_status => {\n            running => 1,\n            runningmachine => 'pc-q35-5.0+pve0',\n        },\n        opts => {\n            online => 1,\n            'with-local-disks' => 1,\n        },\n        expected_calls => $default_expected_calls_online,\n        expected => {\n            source_volids => {},\n            target_volids => {\n                'local-zfs:vm-111-disk-0' => 1,\n                'local-lvm:vm-111-disk-10' => 1,\n            },\n            vm_config => get_patched_config(\n                111,\n                {\n                    ide0 => 'local-lvm:vm-111-disk-10,format=raw,size=4G',\n                    pending => {\n                        scsi0 => 'local-zfs:vm-111-disk-0,size=103M',\n                    },\n                },\n            ),\n            vm_status => {\n                running => 1,\n                runningmachine => 'pc-q35-5.0+pve0',\n            },\n        },\n    },\n    {\n        name => '123_alias_fail',\n        target => 'pve1',\n        vmid => 123,\n        vm_status => {\n            running => 0,\n        },\n        opts => {\n            'with-local-disks' => 1,\n        },\n        expected_calls => {},\n        expect_die => \"detected not supported aliased volumes\",\n        expected => {\n            source_volids => local_volids_for_vm(123),\n            target_volids => {},\n            vm_config => $vm_configs->{123},\n            vm_status => {\n                running => 0,\n            },\n        },\n    },\n];\n\nmy $single_test_name = shift;\n\nmkdir $RUN_DIR_PATH;\n\nforeach my $test (@{$tests}) {\n    my $name = $test->{name};\n    next if defined($single_test_name) && $name ne $single_test_name;\n\n    my $run_dir = \"${RUN_DIR_PATH}/${name}\";\n\n    mkdir $run_dir;\n    file_set_contents(\"${run_dir}/replication_config\", to_json($replication_config));\n    file_set_contents(\"${run_dir}/storage_config\", to_json($storage_config));\n    file_set_contents(\"${run_dir}/source_vdisks\", to_json($source_vdisks));\n\n    my $expect_die = $test->{expect_die};\n    my $expected = $test->{expected};\n\n    my $source_volids = local_volids_for_vm($test->{vmid});\n    my $target_volids = $test->{target_volids} // {};\n\n    my $config_patch = $test->{config_patch};\n    my $vm_config = get_patched_config($test->{vmid}, $test->{config_patch});\n\n    my $fail_config = $test->{fail_config} // {};\n    my $storage_migrate_map = $test->{storage_migrate_map} // {};\n\n    if (my $targetstorage = $test->{opts}->{targetstorage}) {\n        $test->{opts}->{storagemap} =\n            PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id');\n    }\n\n    my $migrate_params = {\n        target => $test->{target},\n        vmid => $test->{vmid},\n        opts => $test->{opts},\n    };\n\n    file_set_contents(\"${run_dir}/nbd_info\", to_json({}));\n    file_set_contents(\"${run_dir}/source_volids\", to_json($source_volids));\n    file_set_contents(\"${run_dir}/target_volids\", to_json($target_volids));\n    file_set_contents(\"${run_dir}/vm_config\", to_json($vm_config));\n    file_set_contents(\"${run_dir}/vm_status\", to_json($test->{vm_status}));\n    file_set_contents(\"${run_dir}/expected_calls\", to_json($test->{expected_calls}));\n    file_set_contents(\"${run_dir}/fail_config\", to_json($fail_config));\n    file_set_contents(\"${run_dir}/storage_migrate_map\", to_json($storage_migrate_map));\n    file_set_contents(\"${run_dir}/migrate_params\", to_json($migrate_params));\n\n    $ENV{QM_LIB_PATH} = $QM_LIB_PATH;\n    $ENV{RUN_DIR_PATH} = $run_dir;\n    my $exitcode = run_command(\n        [\n            '/usr/bin/perl',\n            \"-I${MIGRATE_LIB_PATH}\",\n            \"-I${MIGRATE_LIB_PATH}/test\",\n            \"${MIGRATE_LIB_PATH}/test/MigrationTest/QemuMigrateMock.pm\",\n        ],\n        noerr => 1,\n        errfunc => sub { print \"#$name - $_[0]\\n\" },\n    );\n\n    if (defined($expect_die) && $exitcode) {\n        my $log = file_get_contents(\"${run_dir}/log\");\n        my @lines = split /\\n/, $log;\n\n        my $matched = 0;\n        foreach my $line (@lines) {\n            $matched = 1 if $line =~ m/^err:.*\\Q${expect_die}\\E/;\n            $matched = 1 if $line =~ m/^warn:.*\\Q${expect_die}\\E/;\n        }\n        if (!$matched) {\n            fail($name);\n            note(\"expected error message is not present in log\");\n        }\n    } elsif (defined($expect_die) && !$exitcode) {\n        fail($name);\n        note(\"mocked migrate call didn't fail, but it was expected to - check log\");\n    } elsif (!defined($expect_die) && $exitcode) {\n        fail($name);\n        note(\"mocked migrate call failed, but it was not expected - check log\");\n    }\n\n    my $expected_calls = decode_json(file_get_contents(\"${run_dir}/expected_calls\"));\n    foreach my $call (keys %{$expected_calls}) {\n        fail($name);\n        note(\"expected call '$call' was not made\");\n    }\n\n    if (!defined($expect_die)) {\n        my $nbd_info = decode_json(file_get_contents(\"${run_dir}/nbd_info\"));\n        foreach my $drive (keys %{$nbd_info}) {\n            fail($name);\n            note(\"drive '$drive' was not mirrored\");\n        }\n    }\n\n    my $actual = {\n        source_volids => decode_json(file_get_contents(\"${run_dir}/source_volids\")),\n        target_volids => decode_json(file_get_contents(\"${run_dir}/target_volids\")),\n        vm_config => decode_json(file_get_contents(\"${run_dir}/vm_config\")),\n        vm_status => decode_json(file_get_contents(\"${run_dir}/vm_status\")),\n    };\n\n    is_deeply($actual, $expected, $name);\n}\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_qemu_restore_config_tests.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse lib qw(..);\n\nuse Test::MockModule;\nuse Test::More;\n\nuse File::Basename;\n\nuse PVE::QemuServer;\nuse PVE::Tools qw(dir_glob_foreach file_get_contents);\n\nmy $INPUT_DIR = './restore-config-input';\nmy $EXPECTED_DIR = './restore-config-expected';\n\n# NOTE update when you add/remove tests\nplan tests => 4;\n\nmy $pve_cluster_module = Test::MockModule->new(\"PVE::Cluster\");\n$pve_cluster_module->mock(\n    cfs_read_file => sub {\n        my ($file) = @_;\n\n        if ($file eq 'datacenter.cfg') {\n            return {};\n        } else {\n            die \"'cfs_read_file' called - missing mock?\\n\";\n        }\n    },\n);\n\ndir_glob_foreach(\n    './restore-config-input',\n    '[0-9]+.conf',\n    sub {\n        my ($file) = @_;\n\n        my $vmid = basename($file, ('.conf'));\n\n        my $fh = IO::File->new(\"${INPUT_DIR}/${file}\", \"r\")\n            or die \"unable to read '$file' - $!\\n\";\n\n        my $map = {};\n        my $disknum = 0;\n\n        # NOTE For now, the map is hardcoded to a file-based 'target' storage.\n        # In the future, the test could be extended to include parse_backup_hints\n        # and restore_allocate_devices. Even better if the config-related logic from\n        # the restore_XYZ_archive functions could become a separate function.\n        while (defined(my $line = <$fh>)) {\n            if ($line =~ m/^\\#qmdump\\#map:(\\S+):(\\S+):(\\S*):(\\S*):$/) {\n                my ($drive, undef, $storeid, $fmt) = ($1, $2, $3, $4);\n\n                $fmt ||= 'raw';\n\n                $map->{$drive} = \"target:${vmid}/vm-${vmid}-disk-${disknum}.${fmt}\";\n                $disknum++;\n            }\n        }\n\n        $fh->seek(0, 0) or die \"seek failed - $!\\n\";\n\n        my $got = '';\n        my $cookie = { netcount => 0 };\n\n        while (defined(my $line = <$fh>)) {\n            $got .= PVE::QemuServer::restore_update_config_line(\n                $cookie, $map, $line, 0,\n            );\n        }\n\n        my $expected = file_get_contents(\"${EXPECTED_DIR}/${file}\");\n\n        is_deeply($got, $expected, $file);\n    },\n);\n\ndone_testing();\n"
  },
  {
    "path": "src/test/run_snapshot_tests.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse TAP::Harness;\n\nmy $harness = TAP::Harness->new({ \"verbosity\" => -2 });\nmy $res = $harness->runtests(\"snapshot-test.pm\");\nsystem(\"rm -rf snapshot-working/\");\nexit -1 if $res->{failed};\n"
  },
  {
    "path": "src/test/snapshot-expected/commit/qemu-server/101.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/commit/qemu-server/102.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/commit/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/commit/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: abcdefg\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[abcdefg]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[abcdefg2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: abcdefg\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/commit/qemu-server/203.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: delete\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/101.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/102.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/103.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/104.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n\n[test2]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/105.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/106.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-2,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-1,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/203.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/301.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/302.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/create/qemu-server/303.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/101.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/102.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/103.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/104.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test3\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test3]\n#another test comment\nbootdisk: ide0\ncores: 2\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/105.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/106.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot-delete\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: delete\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot-delete\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: delete\nsnaptime: 1234567890\nsockets: 1\nunused0: local:snapshotable-disk-1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/203.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: backup\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/delete/qemu-server/204.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/101.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/102.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/103.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/104.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/200.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/prepare/qemu-server/300.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/101.conf",
    "content": "# this is a description\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/102.conf",
    "content": "# this is a description\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/103.conf",
    "content": "# this is a description\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/104.conf",
    "content": "# this is a description\nbootdisk: ide0\ncores: 3\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 3\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test3]\n#another test comment\nbootdisk: ide0\ncores: 2\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/105.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/106.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/201.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/202.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/203.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: delete\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/204.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: backup\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/205.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/206.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:unsnapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:unsnapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/207.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: rollback\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-4,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-4,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/301.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/302.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-expected/rollback/qemu-server/303.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/commit/qemu-server/101.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/commit/qemu-server/102.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/commit/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/commit/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: abcdefg\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[abcdefg]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[abcdefg2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: abcdefg\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: prepare\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/commit/qemu-server/203.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: delete\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/101.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/102.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/103.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/104.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/105.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/106.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-2,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-1,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/203.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/301.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/302.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/create/qemu-server/303.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/101.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/102.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/103.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/104.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test3\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 3\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test3]\n#another test comment\nbootdisk: ide0\ncores: 2\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/105.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/106.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:unsnapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/203.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: backup\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/delete/qemu-server/204.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/101.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/102.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/103.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/104.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/200.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nlock: snapshot\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/201.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/202.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/prepare/qemu-server/300.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: somestore:somedisk,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/101.conf",
    "content": "# this is a description\nagent: 1\nbootdisk: ide2\ncores: 2\nide0: local:snapshotable-disk-1,size=32G\nide2: none,media=cdrom\nmemory: 4096\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 2\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/102.conf",
    "content": "# this is a description\nagent: 1\nbootdisk: ide2\ncores: 2\nide0: local:snapshotable-disk-1,size=32G\nide2: none,media=cdrom\nmachine: pc\nmemory: 4096\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 2\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/103.conf",
    "content": "# this is a description\nagent: 1\nbootdisk: ide2\ncores: 2\nide0: local:snapshotable-disk-1,size=32G\nide2: none,media=cdrom\nmachine: pc\nmemory: 4096\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 2\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/104.conf",
    "content": "# this is a description\nagent: 1\nbootdisk: ide2\ncores: 2\nide0: local:snapshotable-disk-1,size=32G\nide2: none,media=cdrom\nmachine: pc\nmemory: 4096\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 2\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test2]\n#test comment\nbootdisk: ide0\ncores: 3\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n\n[test3]\n#another test comment\nbootdisk: ide0\ncores: 2\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test2\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/105.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/106.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: pc\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/201.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/202.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:unsnapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/203.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnapstate: delete\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/204.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nlock: backup\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/205.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/206.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:unsnapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:unsnapshotable-disk-3,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/207.conf",
    "content": "bootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsata0: local:snapshotable-disk-4,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n\n[test]\n#test comment\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsata0: local:snapshotable-disk-4,discard=on,size=32G\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvirtio0: local:snapshotable-disk-2,discard=on,size=32G\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/301.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/302.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-input/rollback/qemu-server/303.conf",
    "content": "agent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nparent: test\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsockets: 1\nvga: qxl\n\n[test]\n#test comment\nagent: 1\nbootdisk: ide0\ncores: 4\nide0: local:snapshotable-disk-1,discard=on,size=32G\nide2: none,media=cdrom\nmachine: q35\nmemory: 8192\nname: win\nnet0: e1000=12:34:56:78:90:12,bridge=somebr0,firewall=1\nnuma: 0\nostype: win7\nrunningmachine: q35\nsmbios1: uuid=01234567-890a-bcde-f012-34567890abcd\nsnaptime: 1234567890\nsockets: 1\nvga: qxl\nvmstate: somestorage:state-volume\n"
  },
  {
    "path": "src/test/snapshot-test.pm",
    "content": "package PVE::QemuServer; ## no critic\n\nuse strict;\nuse warnings;\n\nuse lib qw(..);\n\nuse PVE::Storage;\nuse PVE::Storage::Plugin;\nuse PVE::QemuServer;\nuse PVE::QemuConfig;\nuse PVE::Tools;\nuse PVE::ReplicationConfig;\n\nuse Test::MockModule;\nuse Test::More;\n\nmy $activate_storage_possible = 1;\nmy $nodename;\nmy $snapshot_possible;\nmy $vol_snapshot_possible = {};\nmy $vol_snapshot_delete_possible = {};\nmy $vol_snapshot_rollback_possible = {};\nmy $vol_snapshot_rollback_enabled = {};\nmy $vol_snapshot = {};\nmy $vol_snapshot_delete = {};\nmy $vol_snapshot_rollback = {};\nmy $running;\nmy $freeze_possible;\nmy $stop_possible;\nmy $save_vmstate_works;\nmy $vm_mon = {};\n\n# Mocked methods\n\nsub mocked_volume_snapshot {\n    my ($storecfg, $volid, $snapname) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"volid undefined\\n\"\n        if !defined($volid);\n    die \"snapname undefined\\n\"\n        if !defined($snapname);\n    if ($vol_snapshot_possible->{$volid}) {\n        if (defined($vol_snapshot->{$volid})) {\n            $vol_snapshot->{$volid} .= \",$snapname\";\n        } else {\n            $vol_snapshot->{$volid} = $snapname;\n        }\n        return 1;\n    } else {\n        die \"volume snapshot disabled\\n\";\n    }\n}\n\nsub mocked_volume_snapshot_delete {\n    my ($storecfg, $volid, $snapname) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"volid undefined\\n\"\n        if !defined($volid);\n    die \"snapname undefined\\n\"\n        if !defined($snapname);\n    if ($vol_snapshot_delete_possible->{$volid}) {\n        if (defined($vol_snapshot_delete->{$volid})) {\n            $vol_snapshot_delete->{$volid} .= \",$snapname\";\n        } else {\n            $vol_snapshot_delete->{$volid} = $snapname;\n        }\n        return 1;\n    } else {\n        die \"volume snapshot delete disabled\\n\";\n    }\n}\n\nsub mocked_volume_snapshot_rollback {\n    my ($storecfg, $volid, $snapname) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"volid undefined\\n\"\n        if !defined($volid);\n    die \"snapname undefined\\n\"\n        if !defined($snapname);\n    if ($vol_snapshot_rollback_enabled->{$volid}) {\n        if (defined($vol_snapshot_rollback->{$volid})) {\n            $vol_snapshot_rollback->{$volid} .= \",$snapname\";\n        } else {\n            $vol_snapshot_rollback->{$volid} = $snapname;\n        }\n        return 1;\n    } else {\n        die \"volume snapshot rollback disabled\\n\";\n    }\n}\n\nsub mocked_volume_rollback_is_possible {\n    my ($storecfg, $volid, $snapname) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"volid undefined\\n\"\n        if !defined($volid);\n    die \"snapname undefined\\n\"\n        if !defined($snapname);\n    return $vol_snapshot_rollback_possible->{$volid}\n        if ($vol_snapshot_rollback_possible->{$volid});\n    die \"volume_rollback_is_possible failed\\n\";\n}\n\nsub mocked_activate_storage {\n    my ($storecfg, $storeid) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"storage activation failed\\n\"\n        if !$activate_storage_possible;\n    return;\n}\n\nsub mocked_activate_volumes {\n    my ($storecfg, $volumes) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"wrong volume - fake vmstate expected!\\n\"\n        if ((scalar @$volumes != 1) || @$volumes[0] ne \"somestorage:state-volume\");\n    return;\n}\n\nsub mocked_deactivate_volumes {\n    my ($storecfg, $volumes) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"wrong volume - fake vmstate expected!\\n\"\n        if ((scalar @$volumes != 1) || @$volumes[0] ne \"somestorage:state-volume\");\n    return;\n}\n\nsub mocked_vdisk_free {\n    my ($storecfg, $vmstate) = @_;\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n    die \"wrong vdisk - fake vmstate expected!\\n\"\n        if ($vmstate ne \"somestorage:state-volume\");\n    return;\n}\n\nsub mocked_run_command {\n    my ($cmd, %param) = @_;\n    my $cmdstring;\n    if (my $ref = ref($cmd)) {\n        $cmdstring = PVE::Tools::cmd2string($cmd);\n        if ($cmdstring =~ m/.*\\/qemu-(un)?freeze.*/) {\n            return 1 if $freeze_possible;\n            die \"qemu-[un]freeze disabled\\n\";\n        }\n        if ($cmdstring =~ m/.*\\/qemu-stop.*--kill.*/) {\n            if ($stop_possible) {\n                $running = 0;\n                return 1;\n            } else {\n                return 0;\n            }\n        }\n    }\n    die \"unexpected run_command call: '$cmdstring', aborting\\n\";\n}\n\n# Testing methods\n\nsub test_file {\n    my ($exp_fn, $real_fn) = @_;\n    my $ret;\n    eval { $ret = system(\"diff -u '$exp_fn' '$real_fn'\"); };\n    die if $@;\n    return !$ret;\n}\n\nsub testcase_prepare {\n    my ($vmid, $snapname, $save_vmstate, $comment, $exp_err) = @_;\n    subtest \"Preparing snapshot '$snapname' for vm '$vmid'\" => sub {\n        plan tests => 2;\n        $@ = undef;\n        eval { PVE::QemuConfig->__snapshot_prepare($vmid, $snapname, $save_vmstate, $comment); };\n        is($@, $exp_err, \"\\$@ correct\");\n        ok(\n            test_file(\n                \"snapshot-expected/prepare/qemu-server/$vmid.conf\",\n                \"snapshot-working/prepare/qemu-server/$vmid.conf\",\n            ),\n            \"config file correct\",\n        );\n    };\n}\n\nsub testcase_commit {\n    my ($vmid, $snapname, $exp_err) = @_;\n    subtest \"Committing snapshot '$snapname' for vm '$vmid'\" => sub {\n        plan tests => 2;\n        $@ = undef;\n        eval { PVE::QemuConfig->__snapshot_commit($vmid, $snapname); };\n        is($@, $exp_err, \"\\$@ correct\");\n        ok(\n            test_file(\n                \"snapshot-expected/commit/qemu-server/$vmid.conf\",\n                \"snapshot-working/commit/qemu-server/$vmid.conf\",\n            ),\n            \"config file correct\",\n        );\n    }\n}\n\nsub testcase_create {\n    my ($vmid, $snapname, $save_vmstate, $comment, $exp_err, $exp_vol_snap, $exp_vol_snap_delete) =\n        @_;\n    subtest \"Creating snapshot '$snapname' for vm '$vmid'\" => sub {\n        plan tests => 4;\n        $vol_snapshot = {};\n        $vol_snapshot_delete = {};\n        $exp_vol_snap = {} if !defined($exp_vol_snap);\n        $exp_vol_snap_delete = {} if !defined($exp_vol_snap_delete);\n        $@ = undef;\n        eval { PVE::QemuConfig->snapshot_create($vmid, $snapname, $save_vmstate, $comment); };\n        is($@, $exp_err, \"\\$@ correct\");\n        is_deeply($vol_snapshot, $exp_vol_snap, \"created correct volume snapshots\");\n        is_deeply(\n            $vol_snapshot_delete,\n            $exp_vol_snap_delete,\n            \"deleted correct volume snapshots\",\n        );\n        ok(\n            test_file(\n                \"snapshot-expected/create/qemu-server/$vmid.conf\",\n                \"snapshot-working/create/qemu-server/$vmid.conf\",\n            ),\n            \"config file correct\",\n        );\n    };\n}\n\nsub testcase_delete {\n    my ($vmid, $snapname, $force, $exp_err, $exp_vol_snap_delete) = @_;\n    subtest \"Deleting snapshot '$snapname' of vm '$vmid'\" => sub {\n        plan tests => 3;\n        $vol_snapshot_delete = {};\n        $exp_vol_snap_delete = {} if !defined($exp_vol_snap_delete);\n        $@ = undef;\n        eval { PVE::QemuConfig->snapshot_delete($vmid, $snapname, $force); };\n        is($@, $exp_err, \"\\$@ correct\");\n        is_deeply(\n            $vol_snapshot_delete,\n            $exp_vol_snap_delete,\n            \"deleted correct volume snapshots\",\n        );\n        ok(\n            test_file(\n                \"snapshot-expected/delete/qemu-server/$vmid.conf\",\n                \"snapshot-working/delete/qemu-server/$vmid.conf\",\n            ),\n            \"config file correct\",\n        );\n    };\n}\n\nsub testcase_rollback {\n    my ($vmid, $snapname, $exp_err, $exp_vol_snap_rollback) = @_;\n    subtest \"Rolling back to snapshot '$snapname' of vm '$vmid'\" => sub {\n        plan tests => 3;\n        $vol_snapshot_rollback = {};\n        $running = 1;\n        $exp_vol_snap_rollback = {} if !defined($exp_vol_snap_rollback);\n        $@ = undef;\n        eval { PVE::QemuConfig->snapshot_rollback($vmid, $snapname); };\n        is($@, $exp_err, \"\\$@ correct\");\n        is_deeply(\n            $vol_snapshot_rollback,\n            $exp_vol_snap_rollback,\n            \"rolled back to correct volume snapshots\",\n        );\n        ok(\n            test_file(\n                \"snapshot-expected/rollback/qemu-server/$vmid.conf\",\n                \"snapshot-working/rollback/qemu-server/$vmid.conf\",\n            ),\n            \"config file correct\",\n        );\n    };\n}\n\n# BEGIN mocked PVE::QemuConfig methods\nsub config_file_lock {\n    return \"snapshot-working/pve-test.lock\";\n}\n\nsub cfs_config_path {\n    my ($class, $vmid, $node) = @_;\n\n    $node = $nodename if !$node;\n    return \"snapshot-working/$node/qemu-server/$vmid.conf\";\n}\n\nsub load_config {\n    my ($class, $vmid, $node) = @_;\n\n    my $filename = $class->cfs_config_path($vmid, $node);\n\n    my $raw = PVE::Tools::file_get_contents($filename);\n\n    my $conf = PVE::QemuServer::parse_vm_config($filename, $raw);\n    return $conf;\n}\n\nsub write_config {\n    my ($class, $vmid, $conf) = @_;\n\n    my $filename = $class->cfs_config_path($vmid);\n\n    if ($conf->{snapshots}) {\n        foreach my $snapname (keys %{ $conf->{snapshots} }) {\n            $conf->{snapshots}->{$snapname}->{snaptime} = \"1234567890\"\n                if $conf->{snapshots}->{$snapname}->{snaptime};\n        }\n    }\n\n    my $raw = PVE::QemuServer::write_vm_config($filename, $conf);\n\n    PVE::Tools::file_set_contents($filename, $raw);\n}\n\nsub has_feature {\n    my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;\n    return $snapshot_possible;\n}\n\nsub __snapshot_save_vmstate {\n    my ($class, $vmid, $conf, $snapname, $storecfg) = @_;\n    die \"save_vmstate failed\\n\"\n        if !$save_vmstate_works;\n\n    my $snap = $conf->{snapshots}->{$snapname};\n    $snap->{vmstate} = \"somestorage:state-volume\";\n    $snap->{runningmachine} = \"q35\";\n}\n\nsub assert_config_exists_on_node {\n    my ($vmid, $node) = @_;\n    return -f cfs_config_path(\"PVE::QemuConfig\", $vmid, $node);\n}\n# END mocked PVE::QemuConfig methods\n\n# BEGIN mocked PVE::QemuServer::Helpers methods\n\nsub vm_running_locally {\n    return $running;\n}\n\n# END mocked PVE::QemuServer::Helpers methods\n\n# BEGIN mocked PVE::QemuServer::Monitor methods\n\nsub qmp_cmd {\n    my ($peer, $execute, %arguments) = @_;\n\n    my $cmd = { execute => $execute, arguments => \\%arguments };\n\n    my $exec = $cmd->{execute};\n    if ($exec eq \"guest-ping\") {\n        die \"guest-ping disabled\\n\"\n            if !$vm_mon->{guest_ping};\n        return;\n    }\n    if ($exec eq \"guest-fsfreeze-freeze\" || $exec eq \"guest-fsfreeze-thaw\") {\n        die \"freeze disabled\\n\"\n            if !$freeze_possible;\n        return;\n    }\n    if ($exec eq \"savevm-start\") {\n        die \"savevm-start disabled\\n\"\n            if !$vm_mon->{savevm_start};\n        return;\n    }\n    if ($exec eq \"savevm-end\") {\n        die \"savevm-end disabled\\n\"\n            if !$vm_mon->{savevm_end};\n        return;\n    }\n    if ($exec eq \"query-savevm\") {\n        return {\n            \"status\" => \"completed\",\n            \"bytes\" => 1024 * 1024 * 1024,\n            \"total-time\" => 5000,\n        };\n    }\n    die \"unexpected vm_qmp_command!\\n\";\n}\n\n# END mocked PVE::QemuServer::Monitor methods\n#\n# BEGIN mocked PVE::QemuMigrate::Helpers methods\n\nsub set_migration_caps { } # ignored\n\n# END mocked PVE::QemuMigrate::Helpers methods\n\n# BEGIN redefine PVE::QemuServer methods\n\nsub do_snapshots_type {\n    return 'storage';\n}\n\nsub vm_start {\n    my ($storecfg, $vmid, $params, $migrate_opts) = @_;\n\n    die \"Storage config not mocked! aborting\\n\"\n        if defined($storecfg);\n\n    die \"statefile and forcemachine must be both defined or undefined! aborting\\n\"\n        if defined($params->{statefile})\n        xor defined($params->{forcemachine});\n\n    return;\n}\n\nsub vm_stop {\n    my (\n        $storecfg,\n        $vmid,\n        $skiplock,\n        $nocheck,\n        $timeout,\n        $shutdown,\n        $force,\n        $keepActive,\n        $migratedfrom,\n    ) = @_;\n\n    $running = 0\n        if $stop_possible;\n\n    return;\n}\n\n# END redefine PVE::QemuServer methods\n\nPVE::Tools::run_command(\"rm -rf snapshot-working\");\nPVE::Tools::run_command(\"cp -a snapshot-input snapshot-working\");\n\nmy $qemu_migrate_helpers_module = Test::MockModule->new('PVE::QemuMigrate::Helpers');\n$qemu_migrate_helpers_module->mock('set_migration_caps', \\&set_migration_caps);\n\nmy $qemu_helpers_module = Test::MockModule->new('PVE::QemuServer::Helpers');\n$qemu_helpers_module->mock('vm_running_locally', \\&vm_running_locally);\n\nmy $qemu_monitor_module = Test::MockModule->new('PVE::QemuServer::Monitor');\n$qemu_monitor_module->mock('qmp_cmd', \\&qmp_cmd);\n\nmy $qemu_config_module = Test::MockModule->new('PVE::QemuConfig');\n$qemu_config_module->mock('config_file_lock', \\&config_file_lock);\n$qemu_config_module->mock('cfs_config_path', \\&cfs_config_path);\n$qemu_config_module->mock('load_config', \\&load_config);\n$qemu_config_module->mock('write_config', \\&write_config);\n$qemu_config_module->mock('has_feature', \\&has_feature);\n$qemu_config_module->mock('__snapshot_save_vmstate', \\&__snapshot_save_vmstate);\n$qemu_config_module->mock('assert_config_exists_on_node', \\&assert_config_exists_on_node);\n\n# ignore existing replication config\nmy $repl_config_module = Test::MockModule->new('PVE::ReplicationConfig');\n$repl_config_module->mock('new' => sub { return bless {}, \"PVE::ReplicationConfig\" });\n$repl_config_module->mock('check_for_existing_jobs' => sub { return });\n\nmy $storage_module = Test::MockModule->new('PVE::Storage');\n$storage_module->mock('config', sub { return; });\n$storage_module->mock('path', sub { return \"/some/store/statefile/path\"; });\n$storage_module->mock('activate_storage', \\&mocked_activate_storage);\n$storage_module->mock('activate_volumes', \\&mocked_activate_volumes);\n$storage_module->mock('deactivate_volumes', \\&mocked_deactivate_volumes);\n$storage_module->mock('vdisk_free', \\&mocked_vdisk_free);\n$storage_module->mock('volume_snapshot', \\&mocked_volume_snapshot);\n$storage_module->mock('volume_snapshot_delete', \\&mocked_volume_snapshot_delete);\n$storage_module->mock('volume_snapshot_rollback', \\&mocked_volume_snapshot_rollback);\n$storage_module->mock('volume_rollback_is_possible', \\&mocked_volume_rollback_is_possible);\n\n$running = 1;\n$freeze_possible = 1;\n$save_vmstate_works = 1;\n\nprintf(\"\\n\");\nprintf(\"Running prepare tests\\n\");\nprintf(\"\\n\");\n$nodename = \"prepare\";\n\nprintf(\"\\n\");\nprintf(\"Setting has_feature to return true\\n\");\nprintf(\"\\n\");\n$snapshot_possible = 1;\n\nprintf(\"Successful snapshot_prepare with no existing snapshots\\n\");\ntestcase_prepare(\"101\", \"test\", 0, \"test comment\", '');\n\nprintf(\"Successful snapshot_prepare with no existing snapshots, including vmstate\\n\");\ntestcase_prepare(\"102\", \"test\", 1, \"test comment\", '');\n\nprintf(\"Successful snapshot_prepare with one existing snapshot\\n\");\ntestcase_prepare(\"103\", \"test2\", 0, \"test comment\", \"\");\n\nprintf(\"Successful snapshot_prepare with one existing snapshot, including vmstate\\n\");\ntestcase_prepare(\"104\", \"test2\", 1, \"test comment\", \"\");\n\nprintf(\"Expected error for snapshot_prepare on locked container\\n\");\ntestcase_prepare(\"200\", \"test\", 0, \"test comment\", \"VM is locked (snapshot)\\n\");\n\nprintf(\"Expected error for snapshot_prepare with duplicate snapshot name\\n\");\ntestcase_prepare(\"201\", \"test\", 0, \"test comment\", \"snapshot name 'test' already used\\n\");\n\n$save_vmstate_works = 0;\n\nprintf(\"Expected error for snapshot_prepare with failing save_vmstate\\n\");\ntestcase_prepare(\"202\", \"test\", 1, \"test comment\", \"save_vmstate failed\\n\");\n\n$save_vmstate_works = 1;\n\nprintf(\"\\n\");\nprintf(\"Setting has_feature to return false\\n\");\nprintf(\"\\n\");\n$snapshot_possible = 0;\n\nprintf(\"Expected error for snapshot_prepare if snapshots not possible\\n\");\ntestcase_prepare(\"300\", \"test\", 0, \"test comment\", \"snapshot feature is not available\\n\");\n\nprintf(\"\\n\");\nprintf(\"Running commit tests\\n\");\nprintf(\"\\n\");\n$nodename = \"commit\";\n\nprintf(\"\\n\");\nprintf(\"Setting has_feature to return true\\n\");\nprintf(\"\\n\");\n$snapshot_possible = 1;\n\nprintf(\"Successful snapshot_commit with one prepared snapshot\\n\");\ntestcase_commit(\"101\", \"test\", \"\");\n\nprintf(\"Successful snapshot_commit with one committed and one prepared snapshot\\n\");\ntestcase_commit(\"102\", \"test2\", \"\");\n\nprintf(\"Expected error for snapshot_commit with no snapshot lock\\n\");\ntestcase_commit(\"201\", \"test\", \"missing snapshot lock\\n\");\n\nprintf(\"Expected error for snapshot_commit with invalid snapshot name\\n\");\ntestcase_commit(\"202\", \"test\", \"snapshot 'test' does not exist\\n\");\n\nprintf(\"Expected error for snapshot_commit with invalid snapshot state\\n\");\ntestcase_commit(\"203\", \"test\", \"wrong snapshot state\\n\");\n\n$vol_snapshot_possible->{\"local:snapshotable-disk-1\"} = 1;\n$vol_snapshot_possible->{\"local:snapshotable-disk-2\"} = 1;\n$vol_snapshot_possible->{\"local:snapshotable-disk-3\"} = 1;\n$vol_snapshot_delete_possible->{\"local:snapshotable-disk-1\"} = 1;\n$vol_snapshot_delete_possible->{\"local:snapshotable-disk-3\"} = 1;\n$vol_snapshot_rollback_enabled->{\"local:snapshotable-disk-1\"} = 1;\n$vol_snapshot_rollback_enabled->{\"local:snapshotable-disk-2\"} = 1;\n$vol_snapshot_rollback_enabled->{\"local:snapshotable-disk-3\"} = 1;\n$vol_snapshot_rollback_possible->{\"local:snapshotable-disk-1\"} = 1;\n$vol_snapshot_rollback_possible->{\"local:snapshotable-disk-2\"} = 1;\n$vol_snapshot_rollback_possible->{\"local:snapshotable-disk-3\"} = 1;\n$vol_snapshot_rollback_possible->{\"local:snapshotable-disk-4\"} = 1;\n$vm_mon->{guest_ping} = 1;\n$vm_mon->{savevm_start} = 1;\n$vm_mon->{savevm_end} = 1;\n\n# possible, but fails\n$vol_snapshot_rollback_possible->{\"local:snapshotable-disk-4\"} = 1;\n\n#printf(\"\\n\");\n#printf(\"Setting up Mocking for PVE::Tools\\n\");\n#my $tools_module = Test::MockModule->new('PVE::Tools');\n#$tools_module->mock('run_command' => \\&mocked_run_command);\n#printf(\"\\trun_command() mocked\\n\");\n#\n$nodename = \"create\";\nprintf(\"\\n\");\nprintf(\"Running create tests\\n\");\nprintf(\"\\n\");\n\nprintf(\"Successful snapshot_create with no existing snapshots\\n\");\ntestcase_create(\"101\", \"test\", 0, \"test comment\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_create with no existing snapshots, including vmstate\\n\");\ntestcase_create(\"102\", \"test\", 1, \"test comment\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_create with one existing snapshots\\n\");\ntestcase_create(\"103\", \"test2\", 0, \"test comment\", \"\", { \"local:snapshotable-disk-1\" => \"test2\" });\n\nprintf(\"Successful snapshot_create with one existing snapshots, including vmstate\\n\");\ntestcase_create(\"104\", \"test2\", 1, \"test comment\", \"\", { \"local:snapshotable-disk-1\" => \"test2\" });\n\nprintf(\"Successful snapshot_create with multiple mps\\n\");\ntestcase_create(\n    \"105\",\n    \"test\",\n    0,\n    \"test comment\",\n    \"\",\n    {\n        \"local:snapshotable-disk-1\" => \"test\",\n        \"local:snapshotable-disk-2\" => \"test\",\n        \"local:snapshotable-disk-3\" => \"test\",\n    },\n);\n\n$freeze_possible = 0;\nprintf(\"Successful snapshot_create with no existing snapshots and broken freeze\\n\");\ntestcase_create(\"106\", \"test\", 1, \"test comment\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n$freeze_possible = 1;\n\nprintf(\"Expected error for snapshot_create when volume snapshot is not possible\\n\");\ntestcase_create(\"201\", \"test\", 0, \"test comment\", \"volume snapshot disabled\\n\\n\");\n\nprintf(\"Expected error for snapshot_create when volume snapshot is not possible for one drive\\n\");\ntestcase_create(\n    \"202\",\n    \"test\",\n    0,\n    \"test comment\",\n    \"volume snapshot disabled\\n\\n\",\n    { \"local:snapshotable-disk-1\" => \"test\" },\n    { \"local:snapshotable-disk-1\" => \"test\" },\n);\n\n$vm_mon->{savevm_start} = 0;\nprintf(\"Expected error for snapshot_create when QEMU mon command 'savevm-start' fails\\n\");\ntestcase_create(\"203\", \"test\", 0, \"test comment\", \"savevm-start disabled\\n\\n\");\n$vm_mon->{savevm_start} = 1;\n\nprintf(\"Successful snapshot_create with no existing snapshots but set machine type\\n\");\ntestcase_create(\"301\", \"test\", 1, \"test comment\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\n$activate_storage_possible = 0;\n\nprintf(\"Expected error for snapshot_create when storage activation is not possible\\n\");\ntestcase_create(\"303\", \"test\", 1, \"test comment\", \"storage activation failed\\n\\n\");\n\n$activate_storage_possible = 1;\n\n$nodename = \"delete\";\nprintf(\"\\n\");\nprintf(\"Running delete tests\\n\");\nprintf(\"\\n\");\n\nprintf(\"Successful snapshot_delete of only existing snapshot\\n\");\ntestcase_delete(\"101\", \"test\", 0, \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_delete of leaf snapshot\\n\");\ntestcase_delete(\"102\", \"test2\", 0, \"\", { \"local:snapshotable-disk-1\" => \"test2\" });\n\nprintf(\"Successful snapshot_delete of root snapshot\\n\");\ntestcase_delete(\"103\", \"test\", 0, \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_delete of intermediate snapshot\\n\");\ntestcase_delete(\"104\", \"test2\", 0, \"\", { \"local:snapshotable-disk-1\" => \"test2\" });\n\nprintf(\"Successful snapshot_delete with broken volume_snapshot_delete and force=1\\n\");\ntestcase_delete(\"105\", \"test\", 1, \"\");\n\nprintf(\"Successful snapshot_delete with mp broken volume_snapshot_delete and force=1\\n\");\ntestcase_delete(\n    \"106\",\n    \"test\",\n    1,\n    \"\",\n    { \"local:snapshotable-disk-1\" => \"test\", \"local:snapshotable-disk-3\" => \"test\" },\n);\n\nprintf(\n    \"Expected error when snapshot_delete fails with broken volume_snapshot_delete and force=0\\n\");\ntestcase_delete(\"201\", \"test\", 0, \"volume snapshot delete disabled\\n\");\n\nprintf(\n    \"Expected error when snapshot_delete fails with broken mp volume_snapshot_delete and force=0\\n\"\n);\ntestcase_delete(\n    \"202\",\n    \"test\",\n    0,\n    \"volume snapshot delete disabled\\n\",\n    { \"local:snapshotable-disk-1\" => \"test\" },\n);\n\nprintf(\"Expected error for snapshot_delete with locked config\\n\");\ntestcase_delete(\"203\", \"test\", 0, \"VM is locked (backup)\\n\");\n\n$activate_storage_possible = 0;\n\nprintf(\"Expected error for snapshot_delete when storage activation is not possible\\n\");\ntestcase_delete(\"204\", \"test\", 0, \"storage activation failed\\n\");\n\n$activate_storage_possible = 1;\n\n$nodename = \"rollback\";\nprintf(\"\\n\");\nprintf(\"Running rollback tests\\n\");\nprintf(\"\\n\");\n\n$stop_possible = 1;\n\nprintf(\"Successful snapshot_rollback to only existing snapshot\\n\");\ntestcase_rollback(\"101\", \"test\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_rollback to leaf snapshot\\n\");\ntestcase_rollback(\"102\", \"test2\", \"\", { \"local:snapshotable-disk-1\" => \"test2\" });\n\nprintf(\"Successful snapshot_rollback to root snapshot\\n\");\ntestcase_rollback(\"103\", \"test\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_rollback to intermediate snapshot\\n\");\ntestcase_rollback(\"104\", \"test2\", \"\", { \"local:snapshotable-disk-1\" => \"test2\" });\n\nprintf(\"Successful snapshot_rollback with multiple mp\\n\");\ntestcase_rollback(\n    \"105\",\n    \"test\",\n    \"\",\n    {\n        \"local:snapshotable-disk-1\" => \"test\",\n        \"local:snapshotable-disk-2\" => \"test\",\n        \"local:snapshotable-disk-3\" => \"test\",\n    },\n);\n\nprintf(\n    \"Successful snapshot_rollback to only existing snapshot, with saved vmstate and machine config\\n\"\n);\ntestcase_rollback(\"106\", \"test\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Expected error for snapshot_rollback with non-existing snapshot\\n\");\ntestcase_rollback(\"201\", \"test2\", \"snapshot 'test2' does not exist\\n\");\n\nprintf(\"Expected error for snapshot_rollback if volume rollback not possible\\n\");\ntestcase_rollback(\"202\", \"test\", \"volume_rollback_is_possible failed\\n\");\n\nprintf(\"Expected error for snapshot_rollback with incomplete snapshot\\n\");\ntestcase_rollback(\"203\", \"test\",\n    \"unable to rollback to incomplete snapshot (snapstate = delete)\\n\");\n\nprintf(\"Expected error for snapshot_rollback with lock\\n\");\ntestcase_rollback(\"204\", \"test\", \"VM is locked (backup)\\n\");\n\n$stop_possible = 0;\n\nprintf(\"Expected error for snapshot_rollback with unkillable container\\n\");\ntestcase_rollback(\"205\", \"test\", \"unable to rollback vm 205: vm is running\\n\");\n\n$stop_possible = 1;\n\nprintf(\"Expected error for snapshot_rollback with mp rollback_is_possible failure\\n\");\ntestcase_rollback(\"206\", \"test\", \"volume_rollback_is_possible failed\\n\");\n\nprintf(\n    \"Expected error for snapshot_rollback with mp rollback failure (results in inconsistent state)\\n\"\n);\ntestcase_rollback(\n    \"207\",\n    \"test\",\n    \"volume snapshot rollback disabled\\n\",\n    { \"local:snapshotable-disk-1\" => \"test\", \"local:snapshotable-disk-2\" => \"test\" },\n);\n\nprintf(\"Successful snapshot_rollback with saved vmstate and machine config only in snapshot\\n\");\ntestcase_rollback(\"301\", \"test\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\nprintf(\"Successful snapshot_rollback with saved vmstate and machine config and runningmachine \\n\");\ntestcase_rollback(\"302\", \"test\", \"\", { \"local:snapshotable-disk-1\" => \"test\" });\n\n$activate_storage_possible = 0;\n\nprintf(\"Expected error for snapshot_rollback when storage activation is not possible\\n\");\ntestcase_rollback(\"303\", \"test\", \"storage activation failed\\n\");\n\n$activate_storage_possible = 1;\n\ndone_testing();\n\n1;\n"
  },
  {
    "path": "src/test/test.vmdk",
    "content": ""
  },
  {
    "path": "src/test/test_get_replicatable_volumes.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse lib ('..');\n\nuse Data::Dumper;\n\nuse PVE::Storage;\nuse PVE::QemuConfig;\n\nuse Test::More;\n\nmy $storecfg = {\n    ids => {\n        local => {\n            type => 'dir',\n            shared => 0,\n            content => {\n                'iso' => 1,\n                'backup' => 1,\n                'images' => 1,\n                'rootdir' => 1,\n            },\n            path => \"/var/lib/vz\",\n        },\n        'local-zfs' => {\n            type => 'zfspool',\n            pool => 'nonexistent-testpool',\n            shared => 0,\n            content => {\n                'images' => 1,\n                'rootdir' => 1,\n            },\n        },\n    },\n};\n\nmy $vmid = 900;\n\nmy $rawconf = \"scsi0: non-existent-store:vm-103-disk-1,size=8G\\n\";\nmy $conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\nmy $volumes;\nmy $expect;\n\nmy $test_name = \"test non existent storage\";\n\neval { $volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0); };\nis($@, \"storage 'non-existent-store' does not exist\\n\", $test_name);\n\n$test_name = \"test with disk from other VM (not owner)\";\n\n$rawconf = \"scsi0: local:103/vm-103-disk-1.qcow2,size=8G\\n\";\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\nis_deeply($volumes, {}, $test_name);\n\n$test_name = \"test missing replicate feature\";\n\n$rawconf = \"scsi0: local:$vmid/vm-$vmid-disk-1.qcow2,size=8G\\n\";\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\neval { $volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0); };\nis($@, \"missing replicate feature on volume 'local:900/vm-900-disk-1.qcow2'\\n\", $test_name);\n\n$test_name = \"test raw path disk with replicate enabled\";\n\n$rawconf = \"scsi0: /dev/disk/abcdefg,size=8G\\n\";\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\neval { $volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0); };\nis($@, \"unable to replicate local file/device '/dev/disk/abcdefg'\\n\", $test_name);\n\n$test_name = \"test raw path disk with replicate disabled\";\n\n$rawconf = \"scsi0: /dev/disk/abcdefg,size=8G,replicate=0\\n\";\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\nis_deeply($volumes, {}, $test_name);\n\n$test_name = \"test CDROM with iso file\";\n\n$rawconf = \"ide2: local:iso/pve-cd.iso,media=cdrom\\n\";\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\nis_deeply($volumes, {}, $test_name);\n\n$test_name = \"test CDROM with access to physical 'cdrom' device\";\n\n$rawconf = \"ide2: cdrom,media=cdrom\\n\";\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\nis_deeply($volumes, {}, $test_name);\n\n$test_name = \"test hidden volid in snapshot\";\n\n$rawconf = <<__EOD__;\nmemory: 1024\nscsi0: local-zfs:vm-$vmid-disk-2,size=8G\n[snap1]\nmemory: 512 \nscsi0: local-zfs:vm-$vmid-disk-1,size=8G    \n__EOD__\n\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\n$expect = {\n    \"local-zfs:vm-$vmid-disk-1\" => 1,\n    \"local-zfs:vm-$vmid-disk-2\" => 1,\n};\nis_deeply($volumes, $expect, $test_name);\n\n$test_name = \"test volid with different replicate setting in snapshot\";\n$rawconf = <<__EOD__;\nmemory: 1024\nscsi0: local-zfs:vm-$vmid-disk-1,size=8G,replicate=0\n[snap1]\nmemory: 512 \nscsi0: local-zfs:vm-$vmid-disk-1,size=8G\n__EOD__\n\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\n$expect = {\n    \"local-zfs:vm-$vmid-disk-1\" => 1,\n};\nis_deeply($volumes, $expect, $test_name);\n\n$test_name = \"test vm with replicatable unused volumes\";\n\n$rawconf = <<__EOD__;\nscsi0: local-zfs:vm-$vmid-disk-1,size=8G\nunused1: local-zfs:vm-$vmid-disk-2\nunused5: local-zfs:vm-$vmid-disk-3\n__EOD__\n\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\n$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);\n$expect = {\n    \"local-zfs:vm-$vmid-disk-1\" => 1,\n    \"local-zfs:vm-$vmid-disk-2\" => 1,\n    \"local-zfs:vm-$vmid-disk-3\" => 1,\n};\nis_deeply($volumes, $expect, $test_name);\n\n$test_name = \"test vm with non-replicatable unused volumes\";\n$rawconf = <<__EOD__;\nscsi0: local-zfs:vm-$vmid-disk-1,size=8G\nunused1: local:$vmid/vm-$vmid-disk-2.raw\n__EOD__\n\n$conf = PVE::QemuServer::parse_vm_config(\"/qemu-server/$vmid.conf\", $rawconf);\neval { $volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0); };\nis($@, \"missing replicate feature on volume 'local:900/vm-900-disk-2.raw'\\n\", $test_name);\n\ndone_testing();\nexit(0);\n"
  },
  {
    "path": "src/usr/Makefile",
    "content": "PACKAGE ?= qemu-server\nDESTDIR=\nPREFIX=/usr\nLIBDIR=$(DESTDIR)/$(PREFIX)/lib\nLIBEXECDIR=$(DESTDIR)/$(PREFIX)/libexec/$(PACKAGE)\nLIBSYSTEMDDIR := $(DESTDIR)/usr/lib/systemd\nDBUSDIR := $(DESTDIR)/usr/share/dbus-1\nSHAREDIR=$(DESTDIR)/$(PREFIX)/share/$(PACKAGE)\n\n.PHONY: install\ninstall: pve-usb.cfg pve-q35.cfg pve-q35-4.0.cfg bootsplash.jpg modules-load.conf pve-bridge pve-bridge-hotplug pve-bridgedown\n\tinstall -d $(SHAREDIR)\n\tinstall -m 0644 pve-usb.cfg $(SHAREDIR)\n\tinstall -m 0644 pve-q35.cfg $(SHAREDIR)\n\tinstall -m 0644 pve-q35-4.0.cfg $(SHAREDIR)\n\tinstall -m 0644 -D bootsplash.jpg $(SHAREDIR)\n\tinstall -D -m 0644 modules-load.conf $(LIBDIR)/modules-load.d/qemu-server.conf\n\tinstall -d $(LIBEXECDIR)\n\tinstall -m 0755 pve-bridge $(LIBEXECDIR)/pve-bridge\n\tinstall -m 0755 pve-bridge-hotplug $(LIBEXECDIR)/pve-bridge-hotplug\n\tinstall -m 0755 pve-bridgedown $(LIBEXECDIR)/pve-bridgedown\n\tinstall -D -m 0755 dbus-vmstate $(LIBEXECDIR)/dbus-vmstate\n\tinstall -d $(LIBSYSTEMDDIR)\n\tinstall -D -m 0644 pve-dbus-vmstate@.service $(LIBSYSTEMDDIR)/system/pve-dbus-vmstate@.service\n\tinstall -d $(DBUSDIR)\n\tinstall -D -m 0644 org.qemu.VMState1.conf $(DBUSDIR)/system.d/org.qemu.VMState1.conf\n\n.PHONY: clean\nclean:\n"
  },
  {
    "path": "src/usr/dbus-vmstate",
    "content": "#!/usr/bin/perl\n\n# Exports an DBus object implementing\n# https://www.qemu.org/docs/master/interop/dbus-vmstate.html\n\npackage PVE::QemuServer::DBusVMState;\n\nuse warnings;\nuse strict;\n\nuse Carp;\nuse Net::DBus;\nuse Net::DBus::Exporter qw(org.qemu.VMState1);\nuse Net::DBus::Reactor;\nuse PVE::QemuServer::Helpers;\nuse PVE::QemuServer::QMPHelpers qw(qemu_objectadd qemu_objectdel);\nuse PVE::SafeSyslog;\nuse PVE::Systemd;\nuse PVE::Tools;\n\nuse base qw(Net::DBus::Object);\n\nuse Class::MethodMaker [ scalar => [ qw(Id NumMigratedEntries) ]];\ndbus_property('Id', 'string', 'read');\ndbus_property('NumMigratedEntries', 'uint32', 'read', 'com.proxmox.VMStateHelper');\n\nsub new {\n    my ($class, $service, $vmid) = @_;\n\n    my $self = $class->SUPER::new($service, '/org/qemu/VMState1');\n    $self->{vmid} = $vmid;\n    $self->Id(\"pve-vmstate-$vmid\");\n    $self->NumMigratedEntries(0);\n\n    bless $self, $class;\n    return $self;\n}\n\nsub Load {\n    my ($self, $bytes) = @_;\n\n    my $len = scalar(@$bytes);\n    return if $len <= 1; # see also the `Save` method\n\n    my $text = pack('c*', @$bytes);\n\n    eval {\n\tPVE::Tools::run_command(\n\t    ['conntrack', '--load-file', '-'],\n\t    input => $text,\n\t);\n    };\n    if (my $err = $@) {\n\tsyslog('warn', \"failed to restore conntrack state: $err\\n\");\n    } else {\n\tsyslog('info', \"restored $len bytes of conntrack state\\n\");\n    }\n}\ndbus_method('Load', [['array', 'byte']], []);\n\nuse constant {\n    # From the documentation:\n    #   https://www.qemu.org/docs/master/interop/dbus-vmstate.html),\n    # > For now, the data amount to be transferred is arbitrarily limited to 1Mb.\n    #\n    # See also qemu/backends/dbus-vmstate.c:DBUS_VMSTATE_SIZE_LIMIT\n    DBUS_VMSTATE_SIZE_LIMIT => 1024 * 1024,\n};\n\nsub Save {\n    my ($self) = @_;\n\n    my $text = '';\n    my $truncated = 0;\n    my $num_entries = 0;\n    eval {\n\tPVE::Tools::run_command(\n\t    ['conntrack', '--dump', '--mark', $self->{vmid}, '--output', 'save'],\n\t    outfunc => sub {\n\t\tmy ($line) = @_;\n\t\treturn if $truncated;\n\n\t\tif ((length($text) + length($line)) > DBUS_VMSTATE_SIZE_LIMIT) {\n\t\t   syslog('warn', 'conntrack state too large, ignoring further entries');\n\t\t   $truncated = 1;\n\t\t   return;\n\t\t}\n\n\t\t# conntrack(8) apparently does not preserve the `--mark` option,\n\t\t# add it back ourselves\n\t\t$text .= \"$line --mark $self->{vmid}\\n\";\n\t    },\n\t    errfunc => sub {\n\t\tmy ($line) = @_;\n\n\t\tif ($line =~ /(\\d) flow entries/) {\n\t\t    syslog('info', \"received $1 conntrack entries\");\n\t\t    # conntrack reports the number of displayed entries on stderr,\n\t\t    # which shouldn't be considered an error.\n\t\t    $self->NumMigratedEntries($1);\n\t\t    return;\n\t\t}\n\t\tsyslog('err', $line);\n\t    }\n\t);\n    };\n    if (my $err = $@) {\n\tsyslog('warn', \"failed to save conntrack state: $err\\n\");\n\n\t# Apparently either Net::DBus does not correctly zero-sized (byte)\n\t# arrays correctly - returning [] yields QEMU failing with\n\t#\n\t#   \"kvm: dbus_save_state_proxy: Failed to Save: not a byte array\"\n\t#\n\t# Thus, just return an array with a single element and detect that\n\t# appropriately in the `Load`. A valid conntrack state can *never* be\n\t# just a single byte, so it is safe to rely on that.\n\treturn [0];\n    }\n\n    my @bytes = unpack('c*', $text);\n    my $len = scalar(@bytes);\n\n    syslog('info', \"transferring $len bytes of conntrack state\\n\");\n\n    # Same as above w.r.t. returning as single-element array.\n    return $len == 0 ? [0] : \\@bytes;\n}\ndbus_method('Save', [], [['array', 'byte']]);\n\n# Additional method for cleanly shutting down the service.\nsub Quit {\n    my ($self) = @_;\n\n    syslog('info', \"shutting down gracefully ..\\n\");\n\n    # On the source side, the VM won't exist anymore, so no need to remove\n    # anything.\n    if (PVE::QemuServer::Helpers::vm_running_locally($self->{vmid})) {\n\teval { qemu_objectdel($self->{vmid}, 'pve-vmstate') };\n\tif (my $err = $@) {\n\t    syslog('warn', \"failed to remove object: $err\\n\");\n\t}\n    }\n\n    Net::DBus::Reactor->main()->shutdown();\n}\ndbus_method('Quit', [], [], 'com.proxmox.VMStateHelper', { no_return => 1 });\n\nmy $vmid = shift;\n\nmy $dbus = Net::DBus->system();\nmy $service = $dbus->export_service('org.qemu.VMState1');\nmy $obj = PVE::QemuServer::DBusVMState->new($service, $vmid);\n\n$SIG{TERM} = sub {\n    $obj->Quit();\n};\n\nmy $addr = $dbus->get_unique_name();\nsyslog('info', \"pve-vmstate-$vmid listening on $addr\\n\");\n\n# Inform QEMU about our running dbus-vmstate helper\nqemu_objectadd($vmid, 'pve-vmstate', 'dbus-vmstate',\n    addr => 'unix:path=/run/dbus/system_bus_socket',\n    'id-list' => \"pve-vmstate-$vmid\",\n);\n\nPVE::Systemd::notify(\"READY=1\\n\");\n\nNet::DBus::Reactor->main()->run();\n"
  },
  {
    "path": "src/usr/modules-load.conf",
    "content": "vhost_net\nmsr\n"
  },
  {
    "path": "src/usr/org.qemu.VMState1.conf",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\"\n        \"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">\n<busconfig>\n  <policy user=\"root\">\n    <allow own=\"org.qemu.VMState1\" />\n    <allow send_destination=\"org.qemu.VMState1\" />\n    <allow receive_sender=\"org.qemu.VMState1\" />\n    <allow send_destination=\"com.proxmox.VMStateHelper\" />\n  </policy>\n</busconfig>\n"
  },
  {
    "path": "src/usr/pve-bridge",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse PVE::Tools qw(run_command);\nuse PVE::Network::SDN::Vnets;\nuse PVE::Network::SDN::Zones;\nuse PVE::Firewall;\n\nuse PVE::QemuServer::Network;\n\nmy $iface = shift;\n\nmy $hotplug = 0;\nif ($iface eq '--hotplug') {\n    $hotplug = 1;\n    $iface = shift;\n}\n\ndie \"no interface specified\\n\" if !$iface;\n\ndie \"got strange interface name '$iface'\\n\"\n    if $iface !~ m/^tap(\\d+)i(\\d+)$/;\n\nmy $vmid = $1;\nmy $netid = \"net$2\";\n\nmy $migratedfrom = $hotplug ? undef : $ENV{PVE_MIGRATED_FROM};\n\nmy $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);\n\nmy $netconf = $conf->{$netid};\n\n$netconf = $conf->{pending}->{$netid} if !$migratedfrom && defined($conf->{pending}->{$netid});\n\ndie \"unable to get network config '$netid'\\n\"\n    if !defined($netconf);\n\nmy $net = PVE::QemuServer::Network::parse_net($netconf);\ndie \"unable to parse network config '$netid'\\n\" if !$net;\n\nPVE::Network::SDN::Vnets::add_dhcp_mapping($net->{bridge}, $net->{macaddr}, $vmid, $conf->{name});\nPVE::Network::SDN::Zones::tap_create($iface, $net->{bridge});\nPVE::QemuServer::Network::tap_plug(\n    $iface, $net->{bridge}, $net->{tag}, $net->{firewall}, $net->{trunks}, $net->{rate},\n);\n\nexit 0;\n"
  },
  {
    "path": "src/usr/pve-bridge-hotplug",
    "content": "#!/bin/sh\n\nexec /usr/libexec/qemu-server/pve-bridge --hotplug \"$@\"\n"
  },
  {
    "path": "src/usr/pve-bridgedown",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nuse PVE::Network;\n\nmy $iface = shift;\n\ndie \"no interface specified\\n\" if !$iface;\n\ndie \"got strange interface name '$iface'\\n\"\n    if $iface !~ m/^tap(\\d+)i(\\d+)$/;\n\nPVE::Network::tap_unplug($iface);\n\nexit 0;\n"
  },
  {
    "path": "src/usr/pve-dbus-vmstate@.service",
    "content": "[Unit]\nDescription=PVE DBus VMState Helper (VM %i)\nRequires=dbus.socket\nAfter=dbus.socket\nPartOf=%i.scope\n\n[Service]\nSlice=qemu.slice\nType=notify\nExecStart=/usr/libexec/qemu-server/dbus-vmstate %i\n"
  },
  {
    "path": "src/usr/pve-q35-4.0.cfg",
    "content": "[device \"ehci\"]\n  driver = \"ich9-usb-ehci1\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.7\"\n\n[device \"uhci-1\"]\n  driver = \"ich9-usb-uhci1\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.0\"\n  masterbus = \"ehci.0\"\n  firstport = \"0\"\n\n[device \"uhci-2\"]\n  driver = \"ich9-usb-uhci2\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.1\"\n  masterbus = \"ehci.0\"\n  firstport = \"2\"\n\n[device \"uhci-3\"]\n  driver = \"ich9-usb-uhci3\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.2\"\n  masterbus = \"ehci.0\"\n  firstport = \"4\"\n\n[device \"ehci-2\"]\n  driver = \"ich9-usb-ehci2\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.7\"\n\n[device \"uhci-4\"]\n  driver = \"ich9-usb-uhci4\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.0\"\n  masterbus = \"ehci-2.0\"\n  firstport = \"0\"\n\n[device \"uhci-5\"]\n  driver = \"ich9-usb-uhci5\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.1\"\n  masterbus = \"ehci-2.0\"\n  firstport = \"2\"\n\n[device \"uhci-6\"]\n  driver = \"ich9-usb-uhci6\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.2\"\n  masterbus = \"ehci-2.0\"\n  firstport = \"4\"\n\n# FIXME: Remove this audio0 device at the next possible time\n#     see: https://pve.proxmox.com/pipermail/pve-devel/2019-July/038417.html\n#          https://pve.proxmox.com/pipermail/pve-devel/2019-July/038428.html\n[device \"audio0\"]\n  driver = \"ich9-intel-hda\"\n  bus = \"pcie.0\"\n  addr = \"1b.0\"\n\n\n[device \"ich9-pcie-port-1\"]\n  driver = \"pcie-root-port\"\n  x-speed = \"16\"\n  x-width = \"32\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.0\"\n  port = \"1\"\n  chassis = \"1\"\n\n[device \"ich9-pcie-port-2\"]\n  driver = \"pcie-root-port\"\n  x-speed = \"16\"\n  x-width = \"32\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.1\"\n  port = \"2\"\n  chassis = \"2\"\n\n[device \"ich9-pcie-port-3\"]\n  driver = \"pcie-root-port\"\n  x-speed = \"16\"\n  x-width = \"32\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.2\"\n  port = \"3\"\n  chassis = \"3\"\n\n[device \"ich9-pcie-port-4\"]\n  driver = \"pcie-root-port\"\n  x-speed = \"16\"\n  x-width = \"32\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.3\"\n  port = \"4\"\n  chassis = \"4\"\n\n##\n# Example PCIe switch with two downstream ports\n#\n#[device \"pcie-switch-upstream-port-1\"]\n#  driver = \"x3130-upstream\"\n#  bus = \"ich9-pcie-port-4\"\n#  addr = \"00.0\"\n#\n#[device \"pcie-switch-downstream-port-1-1\"]\n#  driver = \"xio3130-downstream\"\n#  multifunction = \"on\"\n#  bus = \"pcie-switch-upstream-port-1\"\n#  addr = \"00.0\"\n#  port = \"1\"\n#  chassis = \"5\"\n#\n#[device \"pcie-switch-downstream-port-1-2\"]\n#  driver = \"xio3130-downstream\"\n#  multifunction = \"on\"\n#  bus = \"pcie-switch-upstream-port-1\"\n#  addr = \"00.1\"\n#  port = \"1\"\n#  chassis = \"6\"\n\n\n\n[device \"pcidmi\"]\n  driver = \"i82801b11-bridge\"\n  bus = \"pcie.0\"\n  addr = \"1e.0\"\n\n[device \"pci.0\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"1.0\"\n  chassis_nr = \"1\"\n\n[device \"pci.1\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"2.0\"\n  chassis_nr = \"2\"\n\n[device \"pci.2\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"3.0\"\n  chassis_nr = \"3\"\n\n[device \"pci.3\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"4.0\"\n  chassis_nr = \"4\"\n"
  },
  {
    "path": "src/usr/pve-q35.cfg",
    "content": "[device \"ehci\"]\n  driver = \"ich9-usb-ehci1\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.7\"\n\n[device \"uhci-1\"]\n  driver = \"ich9-usb-uhci1\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.0\"\n  masterbus = \"ehci.0\"\n  firstport = \"0\"\n\n[device \"uhci-2\"]\n  driver = \"ich9-usb-uhci2\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.1\"\n  masterbus = \"ehci.0\"\n  firstport = \"2\"\n\n[device \"uhci-3\"]\n  driver = \"ich9-usb-uhci3\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1d.2\"\n  masterbus = \"ehci.0\"\n  firstport = \"4\"\n\n[device \"ehci-2\"]\n  driver = \"ich9-usb-ehci2\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.7\"\n\n[device \"uhci-4\"]\n  driver = \"ich9-usb-uhci4\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.0\"\n  masterbus = \"ehci-2.0\"\n  firstport = \"0\"\n\n[device \"uhci-5\"]\n  driver = \"ich9-usb-uhci5\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.1\"\n  masterbus = \"ehci-2.0\"\n  firstport = \"2\"\n\n[device \"uhci-6\"]\n  driver = \"ich9-usb-uhci6\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1a.2\"\n  masterbus = \"ehci-2.0\"\n  firstport = \"4\"\n\n\n[device \"audio0\"]\n  driver = \"ich9-intel-hda\"\n  bus = \"pcie.0\"\n  addr = \"1b.0\"\n\n\n[device \"ich9-pcie-port-1\"]\n  driver = \"ioh3420\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.0\"\n  port = \"1\"\n  chassis = \"1\"\n\n[device \"ich9-pcie-port-2\"]\n  driver = \"ioh3420\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.1\"\n  port = \"2\"\n  chassis = \"2\"\n\n[device \"ich9-pcie-port-3\"]\n  driver = \"ioh3420\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.2\"\n  port = \"3\"\n  chassis = \"3\"\n\n[device \"ich9-pcie-port-4\"]\n  driver = \"ioh3420\"\n  multifunction = \"on\"\n  bus = \"pcie.0\"\n  addr = \"1c.3\"\n  port = \"4\"\n  chassis = \"4\"\n\n##\n# Example PCIe switch with two downstream ports\n#\n#[device \"pcie-switch-upstream-port-1\"]\n#  driver = \"x3130-upstream\"\n#  bus = \"ich9-pcie-port-4\"\n#  addr = \"00.0\"\n#\n#[device \"pcie-switch-downstream-port-1-1\"]\n#  driver = \"xio3130-downstream\"\n#  multifunction = \"on\"\n#  bus = \"pcie-switch-upstream-port-1\"\n#  addr = \"00.0\"\n#  port = \"1\"\n#  chassis = \"5\"\n#\n#[device \"pcie-switch-downstream-port-1-2\"]\n#  driver = \"xio3130-downstream\"\n#  multifunction = \"on\"\n#  bus = \"pcie-switch-upstream-port-1\"\n#  addr = \"00.1\"\n#  port = \"1\"\n#  chassis = \"6\"\n\n\n\n[device \"pcidmi\"]\n  driver = \"i82801b11-bridge\"\n  bus = \"pcie.0\"\n  addr = \"1e.0\"\n\n[device \"pci.0\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"1.0\"\n  chassis_nr = \"1\"\n\n[device \"pci.1\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"2.0\"\n  chassis_nr = \"2\"\n\n[device \"pci.2\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"3.0\"\n  chassis_nr = \"3\"\n\n[device \"pci.3\"]\n  driver = \"pci-bridge\"\n  bus = \"pcidmi\"\n  addr = \"4.0\"\n  chassis_nr = \"4\"\n"
  },
  {
    "path": "src/usr/pve-usb.cfg",
    "content": "[device \"ehci\"]\n  driver = \"ich9-usb-ehci1\"\n  addr = \"1d.7\"\n  multifunction = \"on\"\n\n[device \"uhci-1\"]\n  driver = \"ich9-usb-uhci1\"\n  addr = \"1d.0\"\n  multifunction = \"on\"\n  masterbus = \"ehci.0\"\n  firstport = \"0\"\n\n[device \"uhci-2\"]\n  driver = \"ich9-usb-uhci2\"\n  addr = \"1d.1\"\n  multifunction = \"on\"\n  masterbus = \"ehci.0\"\n  firstport = \"2\"\n\n[device \"uhci-3\"]\n  driver = \"ich9-usb-uhci3\"\n  addr = \"1d.2\"\n  multifunction = \"on\"\n  masterbus = \"ehci.0\"\n  firstport = \"4\"\n"
  }
]