]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix #2469: fix qemu-img convert src_format detection
[qemu-server.git] / PVE / QemuServer.pm
index 8c519b5f6ec8801a0d4ff1a38bc88c1b1d7d8232..76146ca22f43d5ae31e60d9aa77d02d53f8625cd 100644 (file)
@@ -3,45 +3,51 @@ package PVE::QemuServer;
 use strict;
 use warnings;
 
-use POSIX;
-use IO::Handle;
-use IO::Select;
-use IO::File;
-use IO::Dir;
-use IO::Socket::UNIX;
+use Cwd 'abs_path';
+use Digest::SHA;
+use Fcntl ':flock';
+use Fcntl;
 use File::Basename;
+use File::Copy qw(copy);
 use File::Path;
 use File::stat;
 use Getopt::Long;
-use Digest::SHA;
-use Fcntl ':flock';
-use Cwd 'abs_path';
+use IO::Dir;
+use IO::File;
+use IO::Handle;
+use IO::Select;
+use IO::Socket::UNIX;
 use IPC::Open3;
 use JSON;
-use Fcntl;
-use PVE::SafeSyslog;
-use Storable qw(dclone);
 use MIME::Base64;
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::Storage;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
-use PVE::JSONSchema qw(get_standard_option);
+use POSIX;
+use Storable qw(dclone);
+use Time::HiRes qw(gettimeofday);
+use URI::Escape;
+use UUID;
+
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::DataCenterConfig;
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::GuestHelpers;
 use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
 use PVE::ProcFSTools;
-use PVE::QemuConfig;
-use PVE::QMPClient;
 use PVE::RPCEnvironment;
-use PVE::GuestHelpers;
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
-use PVE::QemuServer::Memory;
-use PVE::QemuServer::USB qw(parse_usb_device);
-use PVE::QemuServer::Cloudinit;
+use PVE::Storage;
 use PVE::SysFSTools;
 use PVE::Systemd;
-use Time::HiRes qw(gettimeofday);
-use File::Copy qw(copy);
-use URI::Escape;
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach get_host_arch $IPV6RE);
+
+use PVE::QMPClient;
+use PVE::QemuConfig;
+use PVE::QemuServer::Helpers qw(min_version);
+use PVE::QemuServer::Cloudinit;
+use PVE::QemuServer::Machine;
+use PVE::QemuServer::Memory;
+use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
+use PVE::QemuServer::USB qw(parse_usb_device);
 
 my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
 my $OVMF = {
@@ -55,8 +61,6 @@ my $OVMF = {
     ],
 };
 
-my $qemu_snap_storage = { rbd => 1 };
-
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
 
 my $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
@@ -89,7 +93,7 @@ PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
 PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
        description => "Specifies the Qemu machine type.",
        type => 'string',
-       pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?|virt(?:-\d+\.\d+)?)',
+       pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)',
        maxLength => 40,
        optional => 1,
 });
@@ -106,16 +110,6 @@ sub cgroups_write {
 
 my $nodename = PVE::INotify::nodename();
 
-mkdir "/etc/pve/nodes/$nodename";
-my $confdir = "/etc/pve/nodes/$nodename/qemu-server";
-mkdir $confdir;
-
-my $var_run_tmpdir = "/var/run/qemu-server";
-mkdir $var_run_tmpdir;
-
-my $lock_dir = "/var/lock/qemu-server";
-mkdir $lock_dir;
-
 my $cpu_vendor_list = {
     # Intel CPUs
     486 => 'GenuineIntel',
@@ -146,6 +140,9 @@ my $cpu_vendor_list = {
     'Skylake-Client-IBRS' => 'GenuineIntel',
     'Skylake-Server' => 'GenuineIntel',
     'Skylake-Server-IBRS' => 'GenuineIntel',
+    'Cascadelake-Server' => 'GenuineIntel',
+    KnightsMill => 'GenuineIntel',
+
 
     # AMD CPUs
     athlon => 'AuthenticAMD',
@@ -178,7 +175,8 @@ my @supported_cpu_flags = (
     'pdpe1gb',
     'md-clear',
     'hv-tlbflush',
-    'hv-evmcs'
+    'hv-evmcs',
+    'aes'
 );
 my $cpu_flag = qr/[+-](@{[join('|', @supported_cpu_flags)]})/;
 
@@ -245,6 +243,13 @@ my $agent_fmt = {
        optional => 1,
        default => 0
     },
+    type => {
+       description => "Select the agent type",
+       type => 'string',
+       default => 'virtio',
+       optional => 1,
+       enum => [qw(virtio isa)],
+    },
 };
 
 my $vga_fmt = {
@@ -295,6 +300,22 @@ my $audio_fmt = {
     },
 };
 
+my $spice_enhancements_fmt = {
+    foldersharing => {
+       type => 'boolean',
+       optional => 1,
+       default => '0',
+       description =>  "Enable folder sharing via SPICE. Needs Spice-WebDAV daemon installed in the VM."
+    },
+    videostreaming =>  {
+       type => 'string',
+       enum => ['off', 'all', 'filter'],
+       default => 'off',
+       optional => 1,
+       description => "Enable video streaming. Uses compression for detected video streams."
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -410,7 +431,7 @@ win7;; Microsoft Windows 7
 win8;; Microsoft Windows 8/2012/2012r2
 win10;; Microsoft Windows 10/2016
 l24;; Linux 2.4 Kernel
-l26;; Linux 2.6/3.X Kernel
+l26;; Linux 2.6 - 5.X Kernel
 solaris;; Solaris/OpenSolaris/OpenIndiania kernel
 EODESC
     },
@@ -672,6 +693,17 @@ EODESCR
        description => "Configure a audio device, useful in combination with QXL/Spice.",
        optional => 1
     },
+    spice_enhancements => {
+       type => 'string',
+       format => $spice_enhancements_fmt,
+       description => "Configure additional enhancements for SPICE.",
+       optional => 1
+    },
+    tags => {
+       type => 'string', format => 'pve-tag-list',
+       description => 'Tags of the VM. This is only meta information.',
+       optional => 1,
+    },
 };
 
 my $cicustom_fmt = {
@@ -768,7 +800,7 @@ my $MAX_SATA_DISKS = 6;
 my $MAX_USB_DEVICES = 5;
 my $MAX_NETS = 32;
 my $MAX_UNUSED_DISKS = 256;
-my $MAX_HOSTPCI_DEVICES = 4;
+my $MAX_HOSTPCI_DEVICES = 16;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
 my $MAX_NUMA = 8;
@@ -1296,7 +1328,7 @@ EODESCR
     usb3 => {
        optional => 1,
        type => 'boolean',
-       description => "Specifies whether if given host option is a USB3 device or port (this does currently not work reliably with spice redirection and is then ignored).",
+       description => "Specifies whether if given host option is a USB3 device or port.",
         default => 0,
     },
 };
@@ -1308,7 +1340,7 @@ my $usbdesc = {
 };
 PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
 
-my $PCIRE = qr/[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
+my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
 my $hostpci_fmt = {
     host => {
        default_key => 1,
@@ -1471,25 +1503,33 @@ sub kvm_version {
     return $kvm_api_version;
 }
 
-my $kvm_user_version;
+my $kvm_user_version = {};
+my $kvm_mtime = {};
 
 sub kvm_user_version {
+    my ($binary) = @_;
+
+    $binary //= get_command_for_arch(get_host_arch()); # get the native arch by default
+    my $st = stat($binary);
 
-    return $kvm_user_version if $kvm_user_version;
+    my $cachedmtime = $kvm_mtime->{$binary} // -1;
+    return $kvm_user_version->{$binary} if $kvm_user_version->{$binary} &&
+       $cachedmtime == $st->mtime;
 
-    $kvm_user_version = 'unknown';
+    $kvm_user_version->{$binary} = 'unknown';
+    $kvm_mtime->{$binary} = $st->mtime;
 
     my $code = sub {
        my $line = shift;
        if ($line =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?)(\.\d+)?[,\s]/) {
-           $kvm_user_version = $2;
+           $kvm_user_version->{$binary} = $2;
        }
     };
 
-    eval { run_command("kvm -version", outfunc => $code); };
+    eval { run_command([$binary, '--version'], outfunc => $code); };
     warn $@ if $@;
 
-    return $kvm_user_version;
+    return $kvm_user_version->{$binary};
 
 }
 
@@ -1517,29 +1557,7 @@ sub option_exists {
     return defined($confdesc->{$key});
 }
 
-sub nic_models {
-    return $nic_model_list;
-}
-
-sub os_list_description {
-
-    return {
-       other => 'Other',
-       wxp => 'Windows XP',
-       w2k => 'Windows 2000',
-       w2k3 =>, 'Windows 2003',
-       w2k8 => 'Windows 2008',
-       wvista => 'Windows Vista',
-       win7 => 'Windows 7',
-       win8 => 'Windows 8/2012',
-       win10 => 'Windows 10/2016',
-       l24 => 'Linux 2.4',
-       l26 => 'Linux 2.6',
-    };
-}
-
 my $cdrom_path;
-
 sub get_cdrom_path {
 
     return  $cdrom_path if $cdrom_path;
@@ -1800,20 +1818,14 @@ sub path_is_scsi {
     return $res;
 }
 
-sub machine_type_is_q35 {
-    my ($conf) = @_;
-
-    return $conf->{machine} && ($conf->{machine} =~ m/q35/) ? 1 : 0;
-}
-
 sub print_tabletdevice_full {
     my ($conf, $arch) = @_;
 
-    my $q35 = machine_type_is_q35($conf);
+    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
 
     # we use uhci for old VMs because tablet driver was buggy in older qemu
     my $usbbus;
-    if (machine_type_is_q35($conf) || $arch eq 'aarch64') {
+    if (PVE::QemuServer::Machine::machine_type_is_q35($conf) || $arch eq 'aarch64') {
        $usbbus = 'ehci';
     } else {
        $usbbus = 'uhci';
@@ -1862,7 +1874,10 @@ sub print_drivedevice_full {
                 $path = PVE::Storage::path($storecfg, $drive->{file});
            }
 
-           if($path =~ m/^iscsi\:\/\//){
+           # for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)
+           my $version = PVE::QemuServer::Machine::extract_version($machine_type, kvm_user_version());
+           if ($path =~ m/^iscsi\:\/\// &&
+              !min_version($version, 4, 1)) {
                $devicetype = 'generic';
            }
        }
@@ -2130,16 +2145,26 @@ my $vga_map = {
 };
 
 sub print_vga_device {
-    my ($conf, $vga, $arch, $machine, $id, $qxlnum, $bridges) = @_;
+    my ($conf, $vga, $arch, $machine_version, $machine, $id, $qxlnum, $bridges) = @_;
 
     my $type = $vga_map->{$vga->{type}};
     if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {
        $type = 'virtio-gpu';
     }
     my $vgamem_mb = $vga->{memory};
+
+    my $max_outputs = '';
     if ($qxlnum) {
        $type = $id ? 'qxl' : 'qxl-vga';
+
+       if (!$conf->{ostype} || $conf->{ostype} =~ m/^(?:l\d\d)|(?:other)$/) {
+           # set max outputs so linux can have up to 4 qxl displays with one device
+           if (min_version($machine_version, 4, 1)) {
+               $max_outputs = ",max_outputs=4";
+           }
+       }
     }
+
     die "no devicetype for $vga->{type}\n" if !$type;
 
     my $memory = "";
@@ -2160,7 +2185,7 @@ sub print_vga_device {
        $memory = ",ram_size=67108864,vram_size=33554432";
     }
 
-    my $q35 = machine_type_is_q35($conf);
+    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
     my $vgaid = "vga" . ($id // '');
     my $pciaddr;
 
@@ -2171,7 +2196,7 @@ sub print_vga_device {
        $pciaddr = print_pci_addr($vgaid, $bridges, $arch, $machine);
     }
 
-    return "$type,id=${vgaid}${memory}${pciaddr}";
+    return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}";
 }
 
 sub drive_is_cloudinit {
@@ -2314,40 +2339,6 @@ sub vm_is_volid_owner {
     return undef;
 }
 
-sub split_flagged_list {
-    my $text = shift || '';
-    $text =~ s/[,;]/ /g;
-    $text =~ s/^\s+//;
-    return { map { /^(!?)(.*)$/ && ($2, $1) } ($text =~ /\S+/g) };
-}
-
-sub join_flagged_list {
-    my ($how, $lst) = @_;
-    join $how, map { $lst->{$_} . $_ } keys %$lst;
-}
-
-sub vmconfig_delete_pending_option {
-    my ($conf, $key, $force) = @_;
-
-    delete $conf->{pending}->{$key};
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    $pending_delete_hash->{$key} = $force ? '!' : '';
-    $conf->{pending}->{delete} = join_flagged_list(',', $pending_delete_hash);
-}
-
-sub vmconfig_undelete_pending_option {
-    my ($conf, $key) = @_;
-
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    delete $pending_delete_hash->{$key};
-
-    if (%$pending_delete_hash) {
-       $conf->{pending}->{delete} = join_flagged_list(',', $pending_delete_hash);
-    } else {
-       delete $conf->{pending}->{delete};
-    }
-}
-
 sub vmconfig_register_unused_drive {
     my ($storecfg, $vmid, $conf, $drive) = @_;
 
@@ -2362,37 +2353,6 @@ sub vmconfig_register_unused_drive {
     }
 }
 
-sub vmconfig_cleanup_pending {
-    my ($conf) = @_;
-
-    # remove pending changes when nothing changed
-    my $changes;
-    foreach my $opt (keys %{$conf->{pending}}) {
-       if (defined($conf->{$opt}) && ($conf->{pending}->{$opt} eq  $conf->{$opt})) {
-           $changes = 1;
-           delete $conf->{pending}->{$opt};
-       }
-    }
-
-    my $current_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    my $pending_delete_hash = {};
-    while (my ($opt, $force) = each %$current_delete_hash) {
-       if (defined($conf->{$opt})) {
-           $pending_delete_hash->{$opt} = $force;
-       } else {
-           $changes = 1;
-       }
-    }
-
-    if (%$pending_delete_hash) {
-       $conf->{pending}->{delete} = join_flagged_list(',', $pending_delete_hash);
-    } else {
-       delete $conf->{pending}->{delete};
-    }
-
-    return $changes;
-}
-
 # smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str][,base64=bool]
 my $smbios1_fmt = {
     uuid => {
@@ -2575,17 +2535,8 @@ sub check_type {
     }
 }
 
-sub touch_config {
-    my ($vmid) = @_;
-
-    my $conf = PVE::QemuConfig->config_file($vmid);
-    utime undef, undef, $conf;
-}
-
 sub destroy_vm {
-    my ($storecfg, $vmid, $keep_empty_config, $skiplock) = @_;
-
-    my $conffile = PVE::QemuConfig->config_file($vmid);
+    my ($storecfg, $vmid, $skiplock, $replacement_conf) = @_;
 
     my $conf = PVE::QemuConfig->load_config($vmid);
 
@@ -2595,11 +2546,9 @@ sub destroy_vm {
        # check if any base image is still used by a linked clone
        foreach_drive($conf, sub {
                my ($ds, $drive) = @_;
-
                return if drive_is_cdrom($drive);
 
                my $volid = $drive->{file};
-
                return if !$volid || $volid =~ m|^/|;
 
                die "base volume '$volid' is still in use by linked cloned\n"
@@ -2611,43 +2560,31 @@ sub destroy_vm {
     # only remove disks owned by this VM
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
-
        return if drive_is_cdrom($drive, 1);
 
        my $volid = $drive->{file};
-
        return if !$volid || $volid =~ m|^/|;
 
        my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
        return if !$path || !$owner || ($owner != $vmid);
 
-       eval {
-           PVE::Storage::vdisk_free($storecfg, $volid);
-       };
+       eval { PVE::Storage::vdisk_free($storecfg, $volid) };
        warn "Could not remove disk '$volid', check manually: $@" if $@;
-
     });
 
-    if ($keep_empty_config) {
-       PVE::Tools::file_set_contents($conffile, "memory: 128\n");
-    } else {
-       unlink $conffile;
-    }
-
     # also remove unused disk
-    eval {
-       my $dl = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
-
-       eval {
-           PVE::Storage::foreach_volid($dl, sub {
-               my ($volid, $sid, $volname, $d) = @_;
-               PVE::Storage::vdisk_free($storecfg, $volid);
-           });
-       };
+    my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
+    PVE::Storage::foreach_volid($vmdisks, sub {
+       my ($volid, $sid, $volname, $d) = @_;
+       eval { PVE::Storage::vdisk_free($storecfg, $volid) };
        warn $@ if $@;
+    });
 
-    };
-    warn $@ if $@;
+    if (defined $replacement_conf) {
+       PVE::QemuConfig->write_config($vmid, $replacement_conf);
+    } else {
+       PVE::QemuConfig->destroy_config($vmid);
+    }
 }
 
 sub parse_vm_config {
@@ -2803,7 +2740,7 @@ sub write_vm_config {
     &$cleanup_config($conf->{pending}, 1);
 
     foreach my $snapname (keys %{$conf->{snapshots}}) {
-       die "internal error" if $snapname eq 'pending';
+       die "internal error: snapshot name '$snapname' is forbidden" if lc($snapname) eq 'pending';
        &$cleanup_config($conf->{snapshots}->{$snapname}, undef, $snapname);
     }
 
@@ -2894,7 +2831,7 @@ sub check_local_resources {
     push @loc_res, "ivshmem" if $conf->{ivshmem};
 
     foreach my $k (keys %$conf) {
-       next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
+       next if $k =~ m/^usb/ && ($conf->{$k} =~ m/^spice(?![^,])/);
        # sockets are safe: they will recreated be on the target side post-migrate
        next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket');
        push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
@@ -2997,70 +2934,19 @@ sub check_local_storage_availability {
     return $nodehash
 }
 
-sub check_cmdline {
-    my ($pidfile, $pid) = @_;
-
-    my $fh = IO::File->new("/proc/$pid/cmdline", "r");
-    if (defined($fh)) {
-       my $line = <$fh>;
-       $fh->close;
-       return undef if !$line;
-       my @param = split(/\0/, $line);
-
-       my $cmd = $param[0];
-       return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m@(?:^|/)qemu-system-[^/]+$@);
-
-       for (my $i = 0; $i < scalar (@param); $i++) {
-           my $p = $param[$i];
-           next if !$p;
-           if (($p eq '-pidfile') || ($p eq '--pidfile')) {
-               my $p = $param[$i+1];
-               return 1 if $p && ($p eq $pidfile);
-               return undef;
-           }
-       }
-    }
-    return undef;
-}
-
+# Compat only, use assert_config_exists_on_node and vm_running_locally where possible
 sub check_running {
     my ($vmid, $nocheck, $node) = @_;
 
-    my $filename = PVE::QemuConfig->config_file($vmid, $node);
-
-    die "unable to find configuration file for VM $vmid - no such machine\n"
-       if !$nocheck && ! -f $filename;
-
-    my $pidfile = pidfile_name($vmid);
-
-    if (my $fd = IO::File->new("<$pidfile")) {
-       my $st = stat($fd);
-       my $line = <$fd>;
-       close($fd);
-
-       my $mtime = $st->mtime;
-       if ($mtime > time()) {
-           warn "file '$filename' modified in future\n";
-       }
-
-       if ($line =~ m/^(\d+)$/) {
-           my $pid = $1;
-           if (check_cmdline($pidfile, $pid)) {
-               if (my $pinfo = PVE::ProcFSTools::check_process_running($pid)) {
-                   return $pid;
-               }
-           }
-       }
-    }
-
-    return undef;
+    PVE::QemuConfig::assert_config_exists_on_node($vmid, $node) if !$nocheck;
+    return PVE::QemuServer::Helpers::vm_running_locally($vmid);
 }
 
 sub vzlist {
 
     my $vzlist = config_list();
 
-    my $fd = IO::Dir->new($var_run_tmpdir) || return $vzlist;
+    my $fd = IO::Dir->new($PVE::QemuServer::Helpers::var_run_tmpdir) || return $vzlist;
 
     while (defined(my $de = $fd->read)) {
        next if $de !~ m/^(\d+)\.pid$/;
@@ -3143,7 +3029,12 @@ our $vmstatus_return_properties = {
        description => "The current config lock, if any.",
        type => 'string',
        optional => 1,
-    }
+    },
+    tags => {
+       description  => "The current configured tags, if any",
+       type => 'string',
+       optional => 1,
+    },
 };
 
 my $last_proc_pid_stat;
@@ -3168,8 +3059,7 @@ sub vmstatus {
     foreach my $vmid (keys %$list) {
        next if $opt_vmid && ($vmid ne $opt_vmid);
 
-       my $cfspath = PVE::QemuConfig->cfs_config_path($vmid);
-       my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
+       my $conf = PVE::QemuConfig->load_config($vmid);
 
        my $d = { vmid => $vmid };
        $d->{pid} = $list->{$vmid}->{pid};
@@ -3215,6 +3105,7 @@ sub vmstatus {
 
        $d->{serial} = 1 if conf_has_serial($conf);
        $d->{lock} = $conf->{lock} if $conf->{lock};
+       $d->{tags} = $conf->{tags} if defined($conf->{tags});
 
        $res->{$vmid} = $d;
     }
@@ -3454,28 +3345,33 @@ sub vga_conf_has_spice {
     return $1 || 1;
 }
 
-my $host_arch; # FIXME: fix PVE::Tools::get_host_arch
-sub get_host_arch() {
-    $host_arch = (POSIX::uname())[4] if !$host_arch;
-    return $host_arch;
-}
-
 sub is_native($) {
     my ($arch) = @_;
     return get_host_arch() eq $arch;
 }
 
+sub get_vm_arch {
+    my ($conf) = @_;
+    return $conf->{arch} // get_host_arch();
+}
+
 my $default_machines = {
     x86_64 => 'pc',
     aarch64 => 'virt',
 };
 
-sub get_basic_machine_info {
-    my ($conf, $forcemachine) = @_;
+sub get_vm_machine {
+    my ($conf, $forcemachine, $arch, $add_pve_version) = @_;
 
-    my $arch = $conf->{arch} // get_host_arch();
-    my $machine = $forcemachine || $conf->{machine} || $default_machines->{$arch};
-    return ($arch, $machine);
+    my $machine = $forcemachine || $conf->{machine};
+
+    if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
+       $arch //= 'x86_64';
+       $machine ||= $default_machines->{$arch};
+       $machine .= "+pve$PVE::QemuServer::Machine::PVE_MACHINE_VERSION" if $add_pve_version;
+    }
+
+    return $machine;
 }
 
 sub get_ovmf_files($) {
@@ -3501,7 +3397,7 @@ sub get_command_for_arch($) {
 }
 
 sub get_cpu_options {
-    my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_;
+    my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
 
     my $cpuFlags = [];
     my $ostype = $conf->{ostype};
@@ -3525,20 +3421,19 @@ sub get_cpu_options {
 
     push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64';
 
-    push @$cpuFlags , '-x2apic'
-       if $conf->{ostype} && $conf->{ostype} eq 'solaris';
+    push @$cpuFlags , '-x2apic' if $ostype && $ostype eq 'solaris';
 
     push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
 
     push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
 
-    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') {
+    if (min_version($machine_version, 2, 3) && $arch eq 'x86_64') {
 
        push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
        push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
     }
 
-    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
+    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_version, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
 
     push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64';
 
@@ -3566,13 +3461,19 @@ sub config_to_command {
     my $devices = [];
     my $pciaddr = '';
     my $bridges = {};
-    my $kvmver = kvm_user_version();
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
     my $winversion = windows_version($ostype);
     my $kvm = $conf->{kvm};
 
-    my ($arch, $machine_type) = get_basic_machine_info($conf, $forcemachine);
+    my $arch = get_vm_arch($conf);
+    my $kvm_binary = get_command_for_arch($arch);
+    my $kvmver = kvm_user_version($kvm_binary);
+
+    my $add_pve_version = min_version($kvmver, 4, 1);
+
+    my $machine_type = get_vm_machine($conf, $forcemachine, $arch, $add_pve_version);
+    my $machine_version = PVE::QemuServer::Machine::extract_version($machine_type, $kvmver);
     $kvm //= 1 if is_native($arch);
 
     if ($kvm) {
@@ -3588,9 +3489,7 @@ sub config_to_command {
 
     die "detected old qemu-kvm binary ($kvmver)\n" if $vernum < 15000;
 
-    my $have_ovz = -f '/proc/vz/vestat';
-
-    my $q35 = machine_type_is_q35($conf);
+    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
     my $use_old_bios_files = undef;
     ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
@@ -3598,7 +3497,7 @@ sub config_to_command {
     my $cpuunits = defined($conf->{cpuunits}) ?
             $conf->{cpuunits} : $defaults->{cpuunits};
 
-    push @$cmd, get_command_for_arch($arch);
+    push @$cmd, $kvm_binary;
 
     push @$cmd, '-id', $vmid;
 
@@ -3608,16 +3507,16 @@ sub config_to_command {
 
     my $use_virtio = 0;
 
-    my $qmpsocket = qmp_socket($vmid);
+    my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket($vmid);
     push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
     push @$cmd, '-mon', "chardev=qmp,mode=control";
 
-    if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 12)) {
+    if (min_version($machine_version, 2, 12)) {
        push @$cmd, '-chardev', "socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5";
        push @$cmd, '-mon', "chardev=qmp-event,mode=control";
     }
 
-    push @$cmd, '-pidfile' , pidfile_name($vmid);
+    push @$cmd, '-pidfile' , PVE::QemuServer::Helpers::pidfile_name($vmid);
 
     push @$cmd, '-daemonize';
 
@@ -3683,7 +3582,7 @@ sub config_to_command {
     # load q35 config
     if ($q35) {
        # we use different pcie-port hardware for qemu >= 4.0 for passthrough
-       if (qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0)) {
+       if (min_version($machine_version, 4, 0)) {
            push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg';
        } else {
            push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
@@ -3701,7 +3600,7 @@ sub config_to_command {
     if (!$vga->{type}) {
        if ($arch eq 'aarch64') {
            $vga->{type} = 'virtio';
-       } elsif (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
+       } elsif (min_version($machine_version, 2, 9)) {
            $vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
        } else {
            $vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
@@ -3729,67 +3628,66 @@ sub config_to_command {
 
     # host pci devices
     for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
-       my $d = parse_hostpci($conf->{"hostpci$i"});
+       my $id = "hostpci$i";
+       my $d = parse_hostpci($conf->{$id});
        next if !$d;
 
-       my $pcie = $d->{pcie};
-       if ($pcie) {
+       if (my $pcie = $d->{pcie}) {
            die "q35 machine model is not enabled" if !$q35;
            # win7 wants to have the pcie devices directly on the pcie bus
            # instead of in the root port
            if ($winversion == 7) {
-               $pciaddr = print_pcie_addr("hostpci${i}bus0");
+               $pciaddr = print_pcie_addr("${id}bus0");
            } else {
-               $pciaddr = print_pcie_addr("hostpci$i");
+               # add more root ports if needed, 4 are present by default
+               # by pve-q35 cfgs, rest added here on demand.
+               if ($i > 3) {
+                   push @$devices, '-device', print_pcie_root_port($i);
+               }
+               $pciaddr = print_pcie_addr($id);
            }
        } else {
-           $pciaddr = print_pci_addr("hostpci$i", $bridges, $arch, $machine_type);
+           $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type);
        }
 
-       my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
-       my $romfile = $d->{romfile};
-
        my $xvga = '';
        if ($d->{'x-vga'}) {
-           $xvga = ',x-vga=on';
+           $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');
            $kvm_off = 1;
            $vga->{type} = 'none' if !defined($conf->{vga});
            $gpu_passthrough = 1;
-
-           if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-               $xvga = "";
-           }
        }
+
        my $pcidevices = $d->{pciid};
        my $multifunction = 1 if @$pcidevices > 1;
+
        my $sysfspath;
        if ($d->{mdev} && scalar(@$pcidevices) == 1) {
-           my $id = $pcidevices->[0]->{id};
+           my $pci_id = $pcidevices->[0]->{id};
            my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
-           $sysfspath = "/sys/bus/pci/devices/0000:$id/$uuid";
+           $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid";
        } elsif ($d->{mdev}) {
-           warn "ignoring mediated device with multifunction device\n";
+           warn "ignoring mediated device '$id' with multifunction device\n";
        }
 
        my $j=0;
-        foreach my $pcidevice (@$pcidevices) {
-
-           my $id = "hostpci$i";
-           $id .= ".$j" if $multifunction;
-           my $addr = $pciaddr;
-           $addr .= ".$j" if $multifunction;
+       foreach my $pcidevice (@$pcidevices) {
            my $devicestr = "vfio-pci";
+
            if ($sysfspath) {
                $devicestr .= ",sysfsdev=$sysfspath";
            } else {
                $devicestr .= ",host=$pcidevice->{id}";
            }
-           $devicestr .= ",id=$id$addr";
 
-           if($j == 0){
-               $devicestr .= "$rombar$xvga";
+           my $mf_addr = $multifunction ? ".$j" : '';
+           $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";
+
+           if ($j == 0) {
+               $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
+               $devicestr .= "$xvga";
                $devicestr .= ",multifunction=on" if $multifunction;
-               $devicestr .= ",romfile=/usr/share/kvm/$romfile" if $romfile;
+               $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
            }
 
            push @$devices, '-device', $devicestr;
@@ -3798,7 +3696,10 @@ sub config_to_command {
     }
 
     # usb devices
-    my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES);
+    my $usb_dev_features = {};
+    $usb_dev_features->{spice_usb3} = 1 if min_version($machine_version, 4, 0);
+
+    my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features);
     push @$devices, @usbdevices if @usbdevices;
     # serial devices
     for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++)  {
@@ -3865,7 +3766,7 @@ sub config_to_command {
     die "MAX $allowed_vcpus vcpus allowed per VM on this node\n"
        if ($allowed_vcpus < $maxcpus);
 
-    if($hotplug_features->{cpu} && qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 7)) {
+    if($hotplug_features->{cpu} && min_version($machine_version, 2, 7)) {
 
        push @$cmd, '-smp', "1,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
         for (my $i = 2; $i <= $vcpus; $i++)  {
@@ -3895,8 +3796,8 @@ sub config_to_command {
     push @$cmd, '-no-reboot' if  defined($conf->{reboot}) && $conf->{reboot} == 0;
 
     if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
-       push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, undef, $qxlnum, $bridges);
-       my $socket = vnc_socket($vmid);
+       push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, undef, $qxlnum, $bridges);
+       my $socket = PVE::QemuServer::Helpers::vnc_socket($vmid);
        push @$cmd,  '-vnc', "unix:$socket,password";
     } else {
        push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
@@ -3938,7 +3839,7 @@ sub config_to_command {
        push @$rtcFlags, 'base=localtime';
     }
 
-    push @$cmd, get_cpu_options($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough);
+    push @$cmd, get_cpu_options($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough);
 
     PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd);
 
@@ -3946,12 +3847,19 @@ sub config_to_command {
 
     push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
 
-    if (parse_guest_agent($conf)->{enabled}) {
-       my $qgasocket = qmp_socket($vmid, 1);
-       my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
+    my $guest_agent = parse_guest_agent($conf);
+
+    if ($guest_agent->{enabled}) {
+       my $qgasocket = PVE::QemuServer::Helpers::qmp_socket($vmid, 1);
        push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
-       push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
-       push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
+
+       if (!$guest_agent->{type} || $guest_agent->{type} eq 'virtio') {
+           my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
+           push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
+           push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
+       } elsif ($guest_agent->{type} eq 'isa') {
+           push @$devices, '-device', "isa-serial,chardev=qga0";
+       }
     }
 
     my $spice_port;
@@ -3960,7 +3868,7 @@ sub config_to_command {
        if ($qxlnum > 1) {
            if ($winversion){
                for(my $i = 1; $i < $qxlnum; $i++){
-                   push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, $i, $qxlnum, $bridges);
+                   push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, $i, $qxlnum, $bridges);
                }
            } else {
                # assume other OS works like Linux
@@ -3980,14 +3888,23 @@ sub config_to_command {
        my $pfamily = PVE::Tools::get_host_address_family($nodename);
        my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);
        die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs;
-       my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
-       $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
-
-       push @$devices, '-spice', "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
 
        push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
        push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
        push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0";
+
+       my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
+       $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
+
+       my $spice_enhancement = PVE::JSONSchema::parse_property_string($spice_enhancements_fmt, $conf->{spice_enhancements} // '');
+       if ($spice_enhancement->{foldersharing}) {
+           push @$devices, '-chardev', "spiceport,id=foldershare,name=org.spice-space.webdav.0";
+           push @$devices, '-device', "virtserialport,chardev=foldershare,name=org.spice-space.webdav.0";
+       }
+
+       my $spice_opts = "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
+       $spice_opts .= ",streaming-video=$spice_enhancement->{videostreaming}" if $spice_enhancement->{videostreaming};
+       push @$devices, '-spice', "$spice_opts";
     }
 
     # enable balloon by default, unless explicitly disabled
@@ -4116,14 +4033,14 @@ sub config_to_command {
 
     if (!$q35) {
        # add pci bridges
-        if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+        if (min_version($machine_version, 2, 3)) {
           $bridges->{1} = 1;
           $bridges->{2} = 1;
        }
 
        $bridges->{3} = 1 if $scsihw =~ m/^virtio-scsi-single/;
 
-       while (my ($k, $v) = each %$bridges) {
+       for my $k (sort {$b cmp $a} keys %$bridges) {
            $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type);
            unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0;
        }
@@ -4139,8 +4056,9 @@ sub config_to_command {
 
     if (my $vmstate = $conf->{vmstate}) {
        my $statepath = PVE::Storage::path($storecfg, $vmstate);
-       PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+       push @$vollist, $vmstate;
        push @$cmd, '-loadstate', $statepath;
+       print "activating and using '$vmstate' as vmstate\n";
     }
 
     # add custom args
@@ -4152,35 +4070,18 @@ sub config_to_command {
     return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
 }
 
-sub vnc_socket {
-    my ($vmid) = @_;
-    return "${var_run_tmpdir}/$vmid.vnc";
-}
-
 sub spice_port {
     my ($vmid) = @_;
 
-    my $res = vm_mon_cmd($vmid, 'query-spice');
+    my $res = mon_cmd($vmid, 'query-spice');
 
     return $res->{'tls-port'} || $res->{'port'} || die "no spice port\n";
 }
 
-sub qmp_socket {
-    my ($vmid, $qga, $name) = @_;
-    my $sockettype = $qga ? 'qga' : 'qmp';
-    my $ext = $name ? '-'.$name : '';
-    return "${var_run_tmpdir}/$vmid$ext.$sockettype";
-}
-
-sub pidfile_name {
-    my ($vmid) = @_;
-    return "${var_run_tmpdir}/$vmid.pid";
-}
-
 sub vm_devices_list {
     my ($vmid) = @_;
 
-    my $res = vm_mon_cmd($vmid, 'query-pci');
+    my $res = mon_cmd($vmid, 'query-pci');
     my $devices_to_check = [];
     my $devices = {};
     foreach my $pcibus (@$res) {
@@ -4199,14 +4100,14 @@ sub vm_devices_list {
        $devices_to_check = $to_check;
     }
 
-    my $resblock = vm_mon_cmd($vmid, 'query-block');
+    my $resblock = mon_cmd($vmid, 'query-block');
     foreach my $block (@$resblock) {
        if($block->{device} =~ m/^drive-(\S+)/){
                $devices->{$1} = 1;
        }
     }
 
-    my $resmice = vm_mon_cmd($vmid, 'query-mice');
+    my $resmice = mon_cmd($vmid, 'query-mice');
     foreach my $mice (@$resmice) {
        if ($mice->{name} eq 'QEMU HID Tablet') {
            $devices->{tablet} = 1;
@@ -4217,7 +4118,7 @@ sub vm_devices_list {
     # for usb devices there is no query-usb
     # but we can iterate over the entries in
     # qom-list path=/machine/peripheral
-    my $resperipheral = vm_mon_cmd($vmid, 'qom-list', path => '/machine/peripheral');
+    my $resperipheral = mon_cmd($vmid, 'qom-list', path => '/machine/peripheral');
     foreach my $per (@$resperipheral) {
        if ($per->{name} =~ m/^usb\d+$/) {
            $devices->{$per->{name}} = 1;
@@ -4230,7 +4131,7 @@ sub vm_devices_list {
 sub vm_deviceplug {
     my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
 
-    my $q35 = machine_type_is_q35($conf);
+    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
 
     my $devices_list = vm_devices_list($vmid);
     return 1 if defined($devices_list->{$deviceid});
@@ -4306,7 +4207,7 @@ sub vm_deviceplug {
 
        return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
 
-       my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
+       my $machine_type = PVE::QemuServer::Machine::qemu_machine_pxe($vmid, $conf);
        my $use_old_bios_files = undef;
        ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
@@ -4398,13 +4299,13 @@ sub qemu_deviceadd {
     $devicefull = "driver=".$devicefull;
     my %options =  split(/[=,]/, $devicefull);
 
-    vm_mon_cmd($vmid, "device_add" , %options);
+    mon_cmd($vmid, "device_add" , %options);
 }
 
 sub qemu_devicedel {
     my ($vmid, $deviceid) = @_;
 
-    my $ret = vm_mon_cmd($vmid, "device_del", id => $deviceid);
+    my $ret = mon_cmd($vmid, "device_del", id => $deviceid);
 }
 
 sub qemu_iothread_add {
@@ -4433,7 +4334,7 @@ sub qemu_iothread_del {
 sub qemu_objectadd {
     my($vmid, $objectid, $qomtype) = @_;
 
-    vm_mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype);
+    mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype);
 
     return 1;
 }
@@ -4441,7 +4342,7 @@ sub qemu_objectadd {
 sub qemu_objectdel {
     my($vmid, $objectid) = @_;
 
-    vm_mon_cmd($vmid, "object-del", id => $objectid);
+    mon_cmd($vmid, "object-del", id => $objectid);
 
     return 1;
 }
@@ -4451,7 +4352,7 @@ sub qemu_driveadd {
 
     my $drive = print_drive_full($storecfg, $vmid, $device);
     $drive =~ s/\\/\\\\/g;
-    my $ret = vm_human_monitor_command($vmid, "drive_add auto \"$drive\"");
+    my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_add auto \"$drive\"");
 
     # If the command succeeds qemu prints: "OK"
     return 1 if $ret =~ m/OK/s;
@@ -4462,7 +4363,7 @@ sub qemu_driveadd {
 sub qemu_drivedel {
     my($vmid, $deviceid) = @_;
 
-    my $ret = vm_human_monitor_command($vmid, "drive_del drive-$deviceid");
+    my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_del drive-$deviceid");
     $ret =~ s/^\s+//;
 
     return 1 if $ret eq "";
@@ -4572,7 +4473,7 @@ sub qemu_add_pci_bridge {
 sub qemu_set_link_status {
     my ($vmid, $device, $up) = @_;
 
-    vm_mon_cmd($vmid, "set_link", name => $device,
+    mon_cmd($vmid, "set_link", name => $device,
               up => $up ? JSON::true : JSON::false);
 }
 
@@ -4582,14 +4483,14 @@ sub qemu_netdevadd {
     my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
     my %options =  split(/[=,]/, $netdev);
 
-    vm_mon_cmd($vmid, "netdev_add",  %options);
+    mon_cmd($vmid, "netdev_add",  %options);
     return 1;
 }
 
 sub qemu_netdevdel {
     my ($vmid, $deviceid) = @_;
 
-    vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
+    mon_cmd($vmid, "netdev_del", id => $deviceid);
 }
 
 sub qemu_usb_hotplug {
@@ -4620,7 +4521,7 @@ sub qemu_usb_hotplug {
 sub qemu_cpu_hotplug {
     my ($vmid, $conf, $vcpus) = @_;
 
-    my $machine_type = PVE::QemuServer::get_current_qemu_machine($vmid);
+    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -4637,14 +4538,14 @@ sub qemu_cpu_hotplug {
 
     if ($vcpus < $currentvcpus) {
 
-       if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
+       if (PVE::QemuServer::Machine::machine_version($machine_type, 2, 7)) {
 
            for (my $i = $currentvcpus; $i > $vcpus; $i--) {
                qemu_devicedel($vmid, "cpu$i");
                my $retry = 0;
                my $currentrunningvcpus = undef;
                while (1) {
-                   $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+                   $currentrunningvcpus = mon_cmd($vmid, "query-cpus");
                    last if scalar(@{$currentrunningvcpus}) == $i-1;
                    raise_param_exc({ vcpus => "error unplugging cpu$i" }) if $retry > 5;
                    $retry++;
@@ -4661,11 +4562,11 @@ sub qemu_cpu_hotplug {
        return;
     }
 
-    my $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+    my $currentrunningvcpus = mon_cmd($vmid, "query-cpus");
     die "vcpus in running vm does not match its configuration\n"
        if scalar(@{$currentrunningvcpus}) != $currentvcpus;
 
-    if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
+    if (PVE::QemuServer::Machine::machine_version($machine_type, 2, 7)) {
 
        for (my $i = $currentvcpus+1; $i <= $vcpus; $i++) {
            my $cpustr = print_cpu_device($conf, $i);
@@ -4674,7 +4575,7 @@ sub qemu_cpu_hotplug {
            my $retry = 0;
            my $currentrunningvcpus = undef;
            while (1) {
-               $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+               $currentrunningvcpus = mon_cmd($vmid, "query-cpus");
                last if scalar(@{$currentrunningvcpus}) == $i;
                raise_param_exc({ vcpus => "error hotplugging cpu$i" }) if $retry > 10;
                sleep 1;
@@ -4687,7 +4588,7 @@ sub qemu_cpu_hotplug {
     } else {
 
        for (my $i = $currentvcpus; $i < $vcpus; $i++) {
-           vm_mon_cmd($vmid, "cpu-add", id => int($i));
+           mon_cmd($vmid, "cpu-add", id => int($i));
        }
     }
 }
@@ -4701,7 +4602,7 @@ sub qemu_block_set_io_throttle {
 
     return if !check_running($vmid) ;
 
-    vm_mon_cmd($vmid, "block_set_io_throttle", device => $deviceid,
+    mon_cmd($vmid, "block_set_io_throttle", device => $deviceid,
        bps => int($bps),
        bps_rd => int($bps_rd),
        bps_wr => int($bps_wr),
@@ -4766,7 +4667,7 @@ sub qemu_block_resize {
 
     return if !$running;
 
-    vm_mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
+    mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
 
 }
 
@@ -4776,7 +4677,7 @@ sub qemu_volume_snapshot {
     my $running = check_running($vmid);
 
     if ($running && do_snapshots_with_qemu($storecfg, $volid)){
-       vm_mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
+       mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
     } else {
        PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
     }
@@ -4798,7 +4699,7 @@ sub qemu_volume_snapshot_delete {
     }
 
     if ($running && do_snapshots_with_qemu($storecfg, $volid)){
-       vm_mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
+       mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
     } else {
        PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
     }
@@ -4817,7 +4718,7 @@ sub set_migration_caps {
        "compress" => 0
     };
 
-    my $supported_capabilities = vm_mon_cmd_nocheck($vmid, "query-migrate-capabilities");
+    my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
 
     for my $supported_capability (@$supported_capabilities) {
        push @$cap_ref, {
@@ -4826,7 +4727,7 @@ sub set_migration_caps {
        };
     }
 
-    vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
+    mon_cmd($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
 }
 
 my $fast_plug_option = {
@@ -4839,6 +4740,7 @@ my $fast_plug_option = {
     'protection' => 1,
     'vmstatestorage' => 1,
     'hookscript' => 1,
+    'tags' => 1,
 };
 
 # hotplug changes in [PENDING]
@@ -4849,7 +4751,8 @@ sub vmconfig_hotplug_pending {
     my ($vmid, $conf, $storecfg, $selection, $errors) = @_;
 
     my $defaults = load_defaults();
-    my ($arch, $machine_type) = get_basic_machine_info($conf, undef);
+    my $arch = get_vm_arch($conf);
+    my $machine_type = get_vm_machine($conf, undef, $arch);
 
     # commit values which do not have any impact on running VM first
     # Note: those option cannot raise errors, we we do not care about
@@ -4876,9 +4779,10 @@ sub vmconfig_hotplug_pending {
 
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
 
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    while (my ($opt, $force) = each %$pending_delete_hash) {
+    my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
+    foreach my $opt (sort keys %$pending_delete_hash) {
        next if $selection && !$selection->{$opt};
+       my $force = $pending_delete_hash->{$opt}->{force};
        eval {
            if ($opt eq 'hotplug') {
                die "skip\n" if ($conf->{hotplug} =~ /memory/);
@@ -4906,7 +4810,7 @@ sub vmconfig_hotplug_pending {
                die "skip\n" if defined($conf->{balloon}) && $conf->{balloon} == 0;
                # here we reset the ballooning value to memory
                my $balloon = $conf->{memory} || $defaults->{memory};
-               vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+               mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
            } elsif ($fast_plug_option->{$opt}) {
                # do nothing
            } elsif ($opt =~ m/^net(\d+)$/) {
@@ -4932,16 +4836,18 @@ sub vmconfig_hotplug_pending {
        } else {
            # save new config if hotplug was successful
            delete $conf->{$opt};
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            PVE::QemuConfig->write_config($vmid, $conf);
            $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        }
     }
 
-    my $apply_pending_cloudinit;
+    my ($apply_pending_cloudinit, $apply_pending_cloudinit_done);
     $apply_pending_cloudinit = sub {
+       return if $apply_pending_cloudinit_done; # once is enough
+       $apply_pending_cloudinit_done = 1; # once is enough
+
        my ($key, $value) = @_;
-       $apply_pending_cloudinit = sub {}; # once is enough
 
        my @cloudinit_opts = keys %$confdesc_cloudinit;
        foreach my $opt (keys %{$conf->{pending}}) {
@@ -4990,13 +4896,14 @@ sub vmconfig_hotplug_pending {
                # allow manual ballooning if shares is set to zero
                if ((defined($conf->{shares}) && ($conf->{shares} == 0))) {
                    my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
-                   vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+                   mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
                }
            } elsif ($opt =~ m/^net(\d+)$/) {
                # some changes can be done without hotplug
                vmconfig_update_net($storecfg, $conf, $hotplug_features->{network},
                                    $vmid, $opt, $value, $arch, $machine_type);
            } elsif (is_valid_drivename($opt)) {
+               die "skip\n" if $opt eq 'efidisk0';
                # some changes can be done without hotplug
                my $drive = parse_drive($opt, $value);
                if (drive_is_cloudinit($drive)) {
@@ -5067,25 +4974,28 @@ sub vmconfig_delete_or_detach_drive {
     }
 }
 
+
+
 sub vmconfig_apply_pending {
     my ($vmid, $conf, $storecfg) = @_;
 
     # cold plug
 
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    while (my ($opt, $force) = each %$pending_delete_hash) {
+    my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
+    foreach my $opt (sort keys %$pending_delete_hash) {
        die "internal error" if $opt =~ m/^unused/;
+       my $force = $pending_delete_hash->{$opt}->{force};
        $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        if (!defined($conf->{$opt})) {
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            PVE::QemuConfig->write_config($vmid, $conf);
        } elsif (is_valid_drivename($opt)) {
            vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            delete $conf->{$opt};
            PVE::QemuConfig->write_config($vmid, $conf);
        } else {
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            delete $conf->{$opt};
            PVE::QemuConfig->write_config($vmid, $conf);
        }
@@ -5263,14 +5173,14 @@ sub vmconfig_update_disk {
            } else { # cdrom
 
                if ($drive->{file} eq 'none') {
-                   vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
+                   mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
                    if (drive_is_cloudinit($old_drive)) {
                        vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);
                    }
                } else {
                    my $path = get_iso_path($storecfg, $vmid, $drive->{file});
-                   vm_mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
-                   vm_mon_cmd($vmid, "change", device => "drive-$opt",target => "$path") if $path;
+                   mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
+                   mon_cmd($vmid, "change", device => "drive-$opt",target => "$path") if $path;
                }
 
                return 1;
@@ -5300,6 +5210,10 @@ sub vm_start {
 
        die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
 
+       # clean up leftover reboot request files
+       eval { clear_reboot_request($vmid); };
+       warn $@ if $@;
+
        if (!$statefile && scalar(keys %{$conf->{pending}})) {
            vmconfig_apply_pending($vmid, $conf, $storecfg);
            $conf = PVE::QemuConfig->load_config($vmid); # update/reload
@@ -5370,7 +5284,35 @@ sub vm_start {
 
        my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
-       my $migrate_port = 0;
+       my $migration_ip;
+       my $get_migration_ip = sub {
+           my ($cidr, $nodename) = @_;
+
+           return $migration_ip if defined($migration_ip);
+
+           if (!defined($cidr)) {
+               my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+               $cidr = $dc_conf->{migration}->{network};
+           }
+
+           if (defined($cidr)) {
+               my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
+
+               die "could not get IP: no address configured on local " .
+                   "node for network '$cidr'\n" if scalar(@$ips) == 0;
+
+               die "could not get IP: multiple addresses configured on local " .
+                   "node for network '$cidr'\n" if scalar(@$ips) > 1;
+
+               $migration_ip = @$ips[0];
+           }
+
+           $migration_ip = PVE::Cluster::remote_node_ip($nodename, 1)
+               if !defined($migration_ip);
+
+           return $migration_ip;
+       };
+
        my $migrate_uri;
        if ($statefile) {
            if ($statefile eq 'tcp') {
@@ -5387,18 +5329,12 @@ sub vm_start {
                }
 
                if ($migration_type eq 'insecure') {
-                   my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
-                   if ($migrate_network_addr) {
-                       $localip = $migrate_network_addr;
-                   } else {
-                       $localip = PVE::Cluster::remote_node_ip($nodename, 1);
-                   }
-
+                   $localip = $get_migration_ip->($migration_network, $nodename);
                    $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
                }
 
                my $pfamily = PVE::Tools::get_host_address_family($nodename);
-               $migrate_port = PVE::Tools::next_migrate_port($pfamily);
+               my $migrate_port = PVE::Tools::next_migrate_port($pfamily);
                $migrate_uri = "tcp:${localip}:${migrate_port}";
                push @$cmd, '-incoming', $migrate_uri;
                push @$cmd, '-S';
@@ -5415,8 +5351,12 @@ sub vm_start {
                push @$cmd, '-incoming', $migrate_uri;
                push @$cmd, '-S';
 
-           } else {
+           } elsif (-e $statefile) {
                push @$cmd, '-loadstate', $statefile;
+           } else {
+               my $statepath = PVE::Storage::path($storecfg, $statefile);
+               push @$vollist, $statefile;
+               push @$cmd, '-loadstate', $statepath;
            }
        } elsif ($paused) {
            push @$cmd, '-S';
@@ -5429,8 +5369,9 @@ sub vm_start {
          my $pcidevices = $d->{pciid};
          foreach my $pcidevice (@$pcidevices) {
                my $pciid = $pcidevice->{id};
+               $pciid = "0000:$pciid" if $pciid !~ m/^[0-9a-f]{4}:/;
 
-               my $info = PVE::SysFSTools::pci_device_info("0000:$pciid");
+               my $info = PVE::SysFSTools::pci_device_info("$pciid");
                die "IOMMU not present\n" if !PVE::SysFSTools::check_iommu_support();
                die "no pci device info for device '$pciid'\n" if !$info;
 
@@ -5512,26 +5453,25 @@ sub vm_start {
        print "migration listens on $migrate_uri\n" if $migrate_uri;
 
        if ($statefile && $statefile ne 'tcp' && $statefile ne 'unix')  {
-           eval { vm_mon_cmd_nocheck($vmid, "cont"); };
+           eval { mon_cmd($vmid, "cont"); };
            warn $@ if $@;
        }
 
        #start nbd server for storage migration
        if ($targetstorage) {
            my $nodename = PVE::INotify::nodename();
-           my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
-           my $localip = $migrate_network_addr ? $migrate_network_addr : PVE::Cluster::remote_node_ip($nodename, 1);
+           my $localip = $get_migration_ip->($migration_network, $nodename);
            my $pfamily = PVE::Tools::get_host_address_family($nodename);
-           $migrate_port = PVE::Tools::next_migrate_port($pfamily);
+           my $storage_migrate_port = PVE::Tools::next_migrate_port($pfamily);
 
-           vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${migrate_port}" } } );
+           mon_cmd($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${storage_migrate_port}" } } );
 
            $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
 
            foreach my $opt (sort keys %$local_volumes) {
                my $volid = $local_volumes->{$opt};
-               vm_mon_cmd_nocheck($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true );
-               my $migrate_storage_uri = "nbd:${localip}:${migrate_port}:exportname=drive-$opt";
+               mon_cmd($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true );
+               my $migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}:exportname=drive-$opt";
                print "storage migration listens on $migrate_storage_uri volume:$volid\n";
            }
        }
@@ -5545,13 +5485,13 @@ sub vm_start {
            if ($spice_port) {
                print "spice listens on port $spice_port\n";
                if ($spice_ticket) {
-                   vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
-                   vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+30");
+                   mon_cmd($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
+                   mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
                }
            }
 
        } else {
-           vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
+           mon_cmd($vmid, "balloon", value => $conf->{balloon}*1024*1024)
                if !$statefile && $conf->{balloon};
 
            foreach my $opt (keys %$conf) {
@@ -5561,16 +5501,18 @@ sub vm_start {
            }
        }
 
-       vm_mon_cmd_nocheck($vmid, 'qom-set',
+       mon_cmd($vmid, 'qom-set',
                    path => "machine/peripheral/balloon0",
                    property => "guest-stats-polling-interval",
                    value => 2) if (!defined($conf->{balloon}) || $conf->{balloon});
 
-       if ($is_suspended && (my $vmstate = $conf->{vmstate})) {
+       if ($is_suspended) {
            print "Resumed VM, removing state\n";
+           if (my $vmstate = $conf->{vmstate}) {
+               PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+               PVE::Storage::vdisk_free($storecfg, $vmstate);
+           }
            delete $conf->@{qw(lock vmstate runningmachine)};
-           PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
-           PVE::Storage::vdisk_free($storecfg, $vmstate);
            PVE::QemuConfig->write_config($vmid, $conf);
        }
 
@@ -5578,69 +5520,19 @@ sub vm_start {
     });
 }
 
-sub vm_mon_cmd {
-    my ($vmid, $execute, %params) = @_;
-
-    my $cmd = { execute => $execute, arguments => \%params };
-    vm_qmp_command($vmid, $cmd);
-}
-
-sub vm_mon_cmd_nocheck {
-    my ($vmid, $execute, %params) = @_;
-
-    my $cmd = { execute => $execute, arguments => \%params };
-    vm_qmp_command($vmid, $cmd, 1);
-}
-
-sub vm_qmp_command {
-    my ($vmid, $cmd, $nocheck) = @_;
-
-    my $res;
-
-    my $timeout;
-    if ($cmd->{arguments}) {
-       $timeout = delete $cmd->{arguments}->{timeout};
-    }
-
-    eval {
-       die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
-       my $sname = qmp_socket($vmid);
-       if (-e $sname) { # test if VM is reasonambe new and supports qmp/qga
-           my $qmpclient = PVE::QMPClient->new();
-
-           $res = $qmpclient->cmd($vmid, $cmd, $timeout);
-       } else {
-           die "unable to open monitor socket\n";
-       }
-    };
-    if (my $err = $@) {
-       syslog("err", "VM $vmid qmp command failed - $err");
-       die $err;
-    }
-
-    return $res;
-}
-
-sub vm_human_monitor_command {
-    my ($vmid, $cmdline) = @_;
-
-    my $cmd = {
-       execute => 'human-monitor-command',
-       arguments => { 'command-line' => $cmdline},
-    };
-
-    return vm_qmp_command($vmid, $cmd);
-}
-
 sub vm_commandline {
     my ($storecfg, $vmid, $snapname) = @_;
 
     my $conf = PVE::QemuConfig->load_config($vmid);
+    my $forcemachine;
 
     if ($snapname) {
        my $snapshot = $conf->{snapshots}->{$snapname};
        die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
 
+       # check for a 'runningmachine' in snapshot
+       $forcemachine = $snapshot->{runningmachine} if $snapshot->{runningmachine};
+
        $snapshot->{digest} = $conf->{digest}; # keep file digest for API
 
        $conf = $snapshot;
@@ -5648,7 +5540,7 @@ sub vm_commandline {
 
     my $defaults = load_defaults();
 
-    my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
+    my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
     return PVE::Tools::cmd2string($cmd);
 }
@@ -5662,7 +5554,7 @@ sub vm_reset {
 
        PVE::QemuConfig->check_lock($conf) if !$skiplock;
 
-       vm_mon_cmd($vmid, "system_reset");
+       mon_cmd($vmid, "system_reset");
     });
 }
 
@@ -5724,7 +5616,85 @@ sub vm_stop_cleanup {
     warn $@ if $@; # avoid errors - just warn
 }
 
-# Note: use $nockeck to skip tests if VM configuration file exists.
+# call only in locked context
+sub _do_vm_stop {
+    my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive) = @_;
+
+    my $pid = check_running($vmid, $nocheck);
+    return if !$pid;
+
+    my $conf;
+    if (!$nocheck) {
+       $conf = PVE::QemuConfig->load_config($vmid);
+       PVE::QemuConfig->check_lock($conf) if !$skiplock;
+       if (!defined($timeout) && $shutdown && $conf->{startup}) {
+           my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
+           $timeout = $opts->{down} if $opts->{down};
+       }
+       PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
+    }
+
+    eval {
+       if ($shutdown) {
+           if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
+               mon_cmd($vmid, "guest-shutdown", timeout => $timeout);
+           } else {
+               mon_cmd($vmid, "system_powerdown");
+           }
+       } else {
+           mon_cmd($vmid, "quit");
+       }
+    };
+    my $err = $@;
+
+    if (!$err) {
+       $timeout = 60 if !defined($timeout);
+
+       my $count = 0;
+       while (($count < $timeout) && check_running($vmid, $nocheck)) {
+           $count++;
+           sleep 1;
+       }
+
+       if ($count >= $timeout) {
+           if ($force) {
+               warn "VM still running - terminating now with SIGTERM\n";
+               kill 15, $pid;
+           } else {
+               die "VM quit/powerdown failed - got timeout\n";
+           }
+       } else {
+           vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
+           return;
+       }
+    } else {
+       if ($force) {
+           warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
+           kill 15, $pid;
+       } else {
+           die "VM quit/powerdown failed\n";
+       }
+    }
+
+    # wait again
+    $timeout = 10;
+
+    my $count = 0;
+    while (($count < $timeout) && check_running($vmid, $nocheck)) {
+       $count++;
+       sleep 1;
+    }
+
+    if ($count >= $timeout) {
+       warn "VM still running - terminating now with SIGKILL\n";
+       kill 9, $pid;
+       sleep 1;
+    }
+
+    vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
+}
+
+# Note: use $nocheck to skip tests if VM configuration file exists.
 # We need that when migration VMs to other nodes (files already moved)
 # Note: we set $keepActive in vzdump stop mode - volumes need to stay active
 sub vm_stop {
@@ -5741,82 +5711,30 @@ sub vm_stop {
     }
 
     PVE::QemuConfig->lock_config($vmid, sub {
+       _do_vm_stop($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive);
+   });
+}
 
-       my $pid = check_running($vmid, $nocheck);
-       return if !$pid;
-
-       my $conf;
-       if (!$nocheck) {
-           $conf = PVE::QemuConfig->load_config($vmid);
-           PVE::QemuConfig->check_lock($conf) if !$skiplock;
-           if (!defined($timeout) && $shutdown && $conf->{startup}) {
-               my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
-               $timeout = $opts->{down} if $opts->{down};
-           }
-           PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
-       }
+sub vm_reboot {
+    my ($vmid, $timeout) = @_;
 
+    PVE::QemuConfig->lock_config($vmid, sub {
        eval {
-           if ($shutdown) {
-               if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
-                   vm_qmp_command($vmid, {
-                       execute => "guest-shutdown",
-                       arguments => { timeout => $timeout }
-                   }, $nocheck);
-               } else {
-                   vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
-               }
-           } else {
-               vm_qmp_command($vmid, { execute => "quit" }, $nocheck);
-           }
-       };
-       my $err = $@;
 
-       if (!$err) {
-           $timeout = 60 if !defined($timeout);
+           # only reboot if running, as qmeventd starts it again on a stop event
+           return if !check_running($vmid);
 
-           my $count = 0;
-           while (($count < $timeout) && check_running($vmid, $nocheck)) {
-               $count++;
-               sleep 1;
-           }
+           create_reboot_request($vmid);
 
-           if ($count >= $timeout) {
-               if ($force) {
-                   warn "VM still running - terminating now with SIGTERM\n";
-                   kill 15, $pid;
-               } else {
-                   die "VM quit/powerdown failed - got timeout\n";
-               }
-           } else {
-               vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
-               return;
-           }
-       } else {
-           if ($force) {
-               warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
-               kill 15, $pid;
-           } else {
-               die "VM quit/powerdown failed\n";
-           }
-       }
+           my $storecfg = PVE::Storage::config();
+           _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1);
 
-       # wait again
-       $timeout = 10;
-
-       my $count = 0;
-       while (($count < $timeout) && check_running($vmid, $nocheck)) {
-           $count++;
-           sleep 1;
-       }
-
-       if ($count >= $timeout) {
-           warn "VM still running - terminating now with SIGKILL\n";
-           kill 9, $pid;
-           sleep 1;
+       };
+       if (my $err = $@) {
+           # avoid that the next normal shutdown will be confused for a reboot
+           clear_reboot_request($vmid);
+           die $err;
        }
-
-       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
    });
 }
 
@@ -5847,7 +5765,7 @@ sub vm_suspend {
            $path = PVE::Storage::path($storecfg, $vmstate);
            PVE::QemuConfig->write_config($vmid, $conf);
        } else {
-           vm_mon_cmd($vmid, "stop");
+           mon_cmd($vmid, "stop");
        }
     });
 
@@ -5856,9 +5774,9 @@ sub vm_suspend {
        PVE::Storage::activate_volumes($storecfg, [$vmstate]);
 
        eval {
-           vm_mon_cmd($vmid, "savevm-start", statefile => $path);
+           mon_cmd($vmid, "savevm-start", statefile => $path);
            for(;;) {
-               my $state = vm_mon_cmd_nocheck($vmid, "query-savevm");
+               my $state = mon_cmd($vmid, "query-savevm");
                if (!$state->{status}) {
                    die "savevm not active\n";
                } elsif ($state->{status} eq 'active') {
@@ -5881,7 +5799,7 @@ sub vm_suspend {
            if ($err) {
                # cleanup, but leave suspending lock, to indicate something went wrong
                eval {
-                   vm_mon_cmd($vmid, "savevm-end");
+                   mon_cmd($vmid, "savevm-end");
                    PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
                    PVE::Storage::vdisk_free($storecfg, $vmstate);
                    delete $conf->@{qw(vmstate runningmachine)};
@@ -5894,7 +5812,7 @@ sub vm_suspend {
            die "lock changed unexpectedly\n"
                if !PVE::QemuConfig->has_lock($conf, 'suspending');
 
-           vm_qmp_command($vmid, { execute => "quit" });
+           mon_cmd($vmid, "quit");
            $conf->{lock} = 'suspended';
            PVE::QemuConfig->write_config($vmid, $conf);
        });
@@ -5905,8 +5823,7 @@ sub vm_resume {
     my ($vmid, $skiplock, $nocheck) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
-       my $vm_mon_cmd = $nocheck ? \&vm_mon_cmd_nocheck : \&vm_mon_cmd;
-       my $res = $vm_mon_cmd->($vmid, 'query-status');
+       my $res = mon_cmd($vmid, 'query-status');
        my $resume_cmd = 'cont';
 
        if ($res->{status} && $res->{status} eq 'suspended') {
@@ -5921,7 +5838,7 @@ sub vm_resume {
                if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
        }
 
-       $vm_mon_cmd->($vmid, $resume_cmd);
+       mon_cmd($vmid, $resume_cmd);
     });
 }
 
@@ -5933,26 +5850,11 @@ sub vm_sendkey {
        my $conf = PVE::QemuConfig->load_config($vmid);
 
        # there is no qmp command, so we use the human monitor command
-       my $res = vm_human_monitor_command($vmid, "sendkey $key");
+       my $res = PVE::QemuServer::Monitor::hmp_cmd($vmid, "sendkey $key");
        die $res if $res ne '';
     });
 }
 
-sub vm_destroy {
-    my ($storecfg, $vmid, $skiplock) = @_;
-
-    PVE::QemuConfig->lock_config($vmid, sub {
-
-       my $conf = PVE::QemuConfig->load_config($vmid);
-
-       if (!check_running($vmid)) {
-           destroy_vm($storecfg, $vmid, undef, $skiplock);
-       } else {
-           die "VM $vmid is running - destroy failed\n";
-       }
-    });
-}
-
 # vzdump restore implementaion
 
 sub tar_archive_read_firstfile {
@@ -6440,7 +6342,7 @@ sub restore_vma_archive {
            foreach_drive($oldconf, sub {
                my ($ds, $drive) = @_;
 
-               return if !$drive->{is_cloudinit} && drive_is_cdrom($drive);
+               return if drive_is_cdrom($drive, 1);
 
                my $volid = $drive->{file};
                return if !$volid || $volid =~ m|^/|;
@@ -6620,9 +6522,11 @@ sub restore_tar_archive {
 
     my $storecfg = PVE::Storage::config();
 
-    # destroy existing data - keep empty config
+    # avoid zombie disks when restoring over an existing VM -> cleanup first
+    # pass keep_empty_config=1 to keep the config (thus VMID) reserved for us
+    # skiplock=1 because qmrestore has set the 'create' lock itself already
     my $vmcfgfn = PVE::QemuConfig->config_file($vmid);
-    destroy_vm($storecfg, $vmid, 1) if -f $vmcfgfn;
+    destroy_vm($storecfg, $vmid, 1, { lock => 'restore' }) if -f $vmcfgfn;
 
     my $tocmd = "/usr/lib/qemu-server/qmextract";
 
@@ -6700,14 +6604,9 @@ sub restore_tar_archive {
        $srcfd->close();
        $outfd->close();
     };
-    my $err = $@;
-
-    if ($err) {
-
+    if (my $err = $@) {
        unlink $tmpfn;
-
        tar_restore_cleanup($storecfg, "$tmpdir/qmrestore.stat") if !$opts->{info};
-
        die $err;
     }
 
@@ -6742,6 +6641,9 @@ sub foreach_storage_used_by_vm {
     }
 }
 
+my $qemu_snap_storage = {
+    rbd => 1,
+};
 sub do_snapshots_with_qemu {
     my ($storecfg, $volid) = @_;
 
@@ -6762,7 +6664,7 @@ sub do_snapshots_with_qemu {
 sub qga_check_running {
     my ($vmid, $nowarn) = @_;
 
-    eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); };
+    eval { mon_cmd($vmid, "guest-ping", timeout => 3); };
     if ($@) {
        warn "Qemu Guest Agent is not running - $@" if !$nowarn;
        return 0;
@@ -6815,66 +6717,78 @@ sub qemu_img_convert {
     my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1);
     my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1);
 
-    if ($src_storeid && $dst_storeid) {
+    die "destination '$dst_volid' is not a valid volid form qemu-img convert\n" if !$dst_storeid;
 
-       PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
+    my $cachemode;
+    my $src_path;
+    my $src_is_iscsi = 0;
+    my $src_format;
 
+    if ($src_storeid) {
+       PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
        my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);
-       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
-
-       my $src_format = qemu_img_format($src_scfg, $src_volname);
-       my $dst_format = qemu_img_format($dst_scfg, $dst_volname);
+       $src_format = qemu_img_format($src_scfg, $src_volname);
+       $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
+       $src_is_iscsi = ($src_path =~ m|^iscsi://|);
+       $cachemode = 'none' if $src_scfg->{type} eq 'zfspool';
+    } elsif (-f $src_volid) {
+       $src_path = $src_volid;
+       if ($src_path =~ m/\.($QEMU_FORMAT_RE)$/) {
+           $src_format = $1;
+       }
+    }
 
-       my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
-       my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+    die "source '$src_volid' is not a valid volid nor path for qemu-img convert\n" if !$src_path;
 
-       my $src_is_iscsi = ($src_path =~ m|^iscsi://|);
-       my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
+    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+    my $dst_format = qemu_img_format($dst_scfg, $dst_volname);
+    my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+    my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
 
-       my $cmd = [];
-       push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
-       push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2");
-       push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
-       push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool';
+    my $cmd = [];
+    push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
+    push @$cmd, '-l', "snapshot.name=$snapname"
+       if $snapname && $src_format && $src_format eq "qcow2";
+    push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
+    push @$cmd, '-T', $cachemode if defined($cachemode);
+
+    if ($src_is_iscsi) {
+       push @$cmd, '--image-opts';
+       $src_path = convert_iscsi_path($src_path);
+    } elsif ($src_format) {
+       push @$cmd, '-f', $src_format;
+    }
+
+    if ($dst_is_iscsi) {
+       push @$cmd, '--target-image-opts';
+       $dst_path = convert_iscsi_path($dst_path);
+    } else {
+       push @$cmd, '-O', $dst_format;
+    }
 
-       if ($src_is_iscsi) {
-           push @$cmd, '--image-opts';
-           $src_path = convert_iscsi_path($src_path);
-       } else {
-           push @$cmd, '-f', $src_format;
-       }
+    push @$cmd, $src_path;
 
-       if ($dst_is_iscsi) {
-           push @$cmd, '--target-image-opts';
-           $dst_path = convert_iscsi_path($dst_path);
-       } else {
-           push @$cmd, '-O', $dst_format;
-       }
+    if (!$dst_is_iscsi && $is_zero_initialized) {
+       push @$cmd, "zeroinit:$dst_path";
+    } else {
+       push @$cmd, $dst_path;
+    }
 
-       push @$cmd, $src_path;
+    my $parser = sub {
+       my $line = shift;
+       if($line =~ m/\((\S+)\/100\%\)/){
+           my $percent = $1;
+           my $transferred = int($size * $percent / 100);
+           my $remaining = $size - $transferred;
 
-       if (!$dst_is_iscsi && $is_zero_initialized) {
-           push @$cmd, "zeroinit:$dst_path";
-       } else {
-           push @$cmd, $dst_path;
+           print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
        }
 
-       my $parser = sub {
-           my $line = shift;
-           if($line =~ m/\((\S+)\/100\%\)/){
-               my $percent = $1;
-               my $transferred = int($size * $percent / 100);
-               my $remaining = $size - $transferred;
-
-               print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
-           }
-
-       };
+    };
 
-       eval  { run_command($cmd, timeout => undef, outfunc => $parser); };
-       my $err = $@;
-       die "copy failed: $err" if $err;
-    }
+    eval  { run_command($cmd, timeout => undef, outfunc => $parser); };
+    my $err = $@;
+    die "copy failed: $err" if $err;
 }
 
 sub qemu_img_format {
@@ -6923,7 +6837,7 @@ sub qemu_drive_mirror {
     }
 
     # if a job already runs for this device we get an error, catch it for cleanup
-    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); };
+    eval { mon_cmd($vmid, "drive-mirror", %$opts); };
     if (my $err = $@) {
        eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
        warn "$@\n" if $@;
@@ -6942,7 +6856,7 @@ sub qemu_drive_mirror_monitor {
        while (1) {
            die "storage migration timed out\n" if $err_complete > 300;
 
-           my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+           my $stats = mon_cmd($vmid, "query-block-jobs");
 
            my $running_mirror_jobs = {};
            foreach my $stat (@$stats) {
@@ -6985,7 +6899,7 @@ sub qemu_drive_mirror_monitor {
                    my $agent_running = $qga && qga_check_running($vmid);
                    if ($agent_running) {
                        print "freeze filesystem\n";
-                       eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+                       eval { mon_cmd($vmid, "guest-fsfreeze-freeze"); };
                    } else {
                        print "suspend vm\n";
                        eval { PVE::QemuServer::vm_suspend($vmid, 1); };
@@ -6996,7 +6910,7 @@ sub qemu_drive_mirror_monitor {
 
                    if ($agent_running) {
                        print "unfreeze filesystem\n";
-                       eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
+                       eval { mon_cmd($vmid, "guest-fsfreeze-thaw"); };
                    } else {
                        print "resume vm\n";
                        eval {  PVE::QemuServer::vm_resume($vmid, 1, 1); };
@@ -7009,7 +6923,7 @@ sub qemu_drive_mirror_monitor {
                        # try to switch the disk if source and destination are on the same guest
                        print "$job: Completing block job...\n";
 
-                       eval { vm_mon_cmd($vmid, "block-job-complete", device => $job) };
+                       eval { mon_cmd($vmid, "block-job-complete", device => $job) };
                        if ($@ =~ m/cannot be completed/) {
                            print "$job: Block job cannot be completed, try again.\n";
                            $err_complete++;
@@ -7037,12 +6951,12 @@ sub qemu_blockjobs_cancel {
 
     foreach my $job (keys %$jobs) {
        print "$job: Cancelling block job\n";
-       eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
+       eval { mon_cmd($vmid, "block-job-cancel", device => $job); };
        $jobs->{$job}->{cancel} = 1;
     }
 
     while (1) {
-       my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+       my $stats = mon_cmd($vmid, "query-block-jobs");
 
        my $running_jobs = {};
        foreach my $stat (@$stats) {
@@ -7085,17 +6999,19 @@ sub clone_disk {
        my $name = undef;
        if (drive_is_cloudinit($drive)) {
            $name = "vm-$newvmid-cloudinit";
+           $name .= ".$dst_format" if $dst_format ne 'raw';
            $snapname = undef;
-           # we only get here if it's supported by QEMU_FORMAT_RE, so just accept
-           if ($dst_format ne 'raw') {
-               $name .= ".$dst_format";
-           }
+           $size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE;
        }
        $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
        push @$newvollist, $newvolid;
 
        PVE::Storage::activate_volumes($storecfg, [$newvolid]);
 
+       if (drive_is_cloudinit($drive)) {
+           goto no_data_clone;
+       }
+
        my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);
        if (!$running || $snapname) {
            # TODO: handle bwlimits
@@ -7103,7 +7019,7 @@ sub clone_disk {
        } else {
 
            my $kvmver = get_running_qemu_version ($vmid);
-           if (!qemu_machine_feature_enabled (undef, $kvmver, 2, 7)) {
+           if (!min_version($kvmver, 2, 7)) {
                die "drive-mirror with iothread requires qemu version 2.7 or higher\n"
                    if $drive->{iothread};
            }
@@ -7112,6 +7028,7 @@ sub clone_disk {
        }
     }
 
+no_data_clone:
     my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
 
     my $disk = $drive;
@@ -7122,64 +7039,12 @@ sub clone_disk {
     return $disk;
 }
 
-# this only works if VM is running
-sub get_current_qemu_machine {
-    my ($vmid) = @_;
-
-    my $cmd = { execute => 'query-machines', arguments => {} };
-    my $res = vm_qmp_command($vmid, $cmd);
-
-    my ($current, $default);
-    foreach my $e (@$res) {
-       $default = $e->{name} if $e->{'is-default'};
-       $current = $e->{name} if $e->{'is-current'};
-    }
-
-    # fallback to the default machine if current is not supported by qemu
-    return $current || $default || 'pc';
-}
-
 sub get_running_qemu_version {
     my ($vmid) = @_;
-    my $cmd = { execute => 'query-version', arguments => {} };
-    my $res = vm_qmp_command($vmid, $cmd);
+    my $res = mon_cmd($vmid, "query-version");
     return "$res->{qemu}->{major}.$res->{qemu}->{minor}";
 }
 
-sub qemu_machine_feature_enabled {
-    my ($machine, $kvmver, $version_major, $version_minor) = @_;
-
-    my $current_major;
-    my $current_minor;
-
-    if ($machine && $machine =~ m/^((?:pc(-i440fx|-q35)?|virt)-(\d+)\.(\d+))/) {
-
-       $current_major = $3;
-       $current_minor = $4;
-
-    } elsif ($kvmver =~ m/^(\d+)\.(\d+)/) {
-
-       $current_major = $1;
-       $current_minor = $2;
-    }
-
-    return 1 if $current_major > $version_major ||
-                ($current_major == $version_major &&
-                 $current_minor >= $version_minor);
-}
-
-sub qemu_machine_pxe {
-    my ($vmid, $conf, $machine) = @_;
-
-    $machine =  PVE::QemuServer::get_current_qemu_machine($vmid) if !$machine;
-
-    if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) {
-       $machine .= '.pxe';
-    }
-
-    return $machine;
-}
-
 sub qemu_use_old_bios_files {
     my ($machine_type) = @_;
 
@@ -7191,12 +7056,12 @@ sub qemu_use_old_bios_files {
         $machine_type = $1;
         $use_old_bios_files = 1;
     } else {
-       my $kvmver = kvm_user_version();
+       my $version = PVE::QemuServer::Machine::extract_version($machine_type, kvm_user_version());
         # Note: kvm version < 2.4 use non-efi pxe files, and have problems when we
         # load new efi bios files on migration. So this hack is required to allow
         # live migration from qemu-2.2 to qemu-2.4, which is sometimes used when
         # updrading from proxmox-ve-3.X to proxmox-ve 4.0
-       $use_old_bios_files = !qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 4);
+       $use_old_bios_files = !min_version($version, 2, 4);
     }
 
     return ($use_old_bios_files, $machine_type);
@@ -7208,23 +7073,21 @@ sub create_efidisk($$$$$) {
     my (undef, $ovmf_vars) = get_ovmf_files($arch);
     die "EFI vars default image not found\n" if ! -f $ovmf_vars;
 
-    my $vars_size = PVE::Tools::convert_size(-s $ovmf_vars, 'b' => 'kb');
+    my $vars_size_b = -s $ovmf_vars;
+    my $vars_size = PVE::Tools::convert_size($vars_size_b, 'b' => 'kb');
     my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
     PVE::Storage::activate_volumes($storecfg, [$volid]);
 
-    my $path = PVE::Storage::path($storecfg, $volid);
-    eval {
-       run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $ovmf_vars, $path]);
-    };
-    die "Copying EFI vars image failed: $@" if $@;
+    qemu_img_convert($ovmf_vars, $volid, $vars_size_b, undef, 0);
+    my ($size) = PVE::Storage::volume_size_info($storecfg, $volid, 3);
 
-    return ($volid, $vars_size);
+    return ($volid, $size/1024);
 }
 
 sub vm_iothreads_list {
     my ($vmid) = @_;
 
-    my $res = vm_mon_cmd($vmid, 'query-iothreads');
+    my $res = mon_cmd($vmid, 'query-iothreads');
 
     my $iothreads = {};
     foreach my $iothread (@$res) {
@@ -7254,7 +7117,7 @@ sub scsihw_infos {
 }
 
 sub add_hyperv_enlightenments {
-    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
+    my ($cpuFlags, $winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
 
     return if $winversion < 6;
     return if $bios && $bios eq 'ovmf' && $winversion < 8;
@@ -7264,7 +7127,7 @@ sub add_hyperv_enlightenments {
        push @$cpuFlags , "hv_vendor_id=$hv_vendor_id";
     }
 
-    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+    if (min_version($machine_version, 2, 3)) {
        push @$cpuFlags , 'hv_spinlocks=0x1fff';
        push @$cpuFlags , 'hv_vapic';
        push @$cpuFlags , 'hv_time';
@@ -7272,7 +7135,7 @@ sub add_hyperv_enlightenments {
        push @$cpuFlags , 'hv_spinlocks=0xffff';
     }
 
-    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
+    if (min_version($machine_version, 2, 6)) {
        push @$cpuFlags , 'hv_reset';
        push @$cpuFlags , 'hv_vpindex';
        push @$cpuFlags , 'hv_runtime';
@@ -7281,12 +7144,12 @@ sub add_hyperv_enlightenments {
     if ($winversion >= 7) {
        push @$cpuFlags , 'hv_relaxed';
 
-       if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) {
+       if (min_version($machine_version, 2, 12)) {
            push @$cpuFlags , 'hv_synic';
            push @$cpuFlags , 'hv_stimer';
        }
 
-       if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) {
+       if (min_version($machine_version, 3, 1)) {
            push @$cpuFlags , 'hv_ipi';
        }
     }
@@ -7357,7 +7220,26 @@ sub generate_smbios1_uuid {
 sub nbd_stop {
     my ($vmid) = @_;
 
-    vm_mon_cmd($vmid, 'nbd-server-stop');
+    mon_cmd($vmid, 'nbd-server-stop');
+}
+
+sub create_reboot_request {
+    my ($vmid) = @_;
+    open(my $fh, '>', "/run/qemu-server/$vmid.reboot")
+       or die "failed to create reboot trigger file: $!\n";
+    close($fh);
+}
+
+sub clear_reboot_request {
+    my ($vmid) = @_;
+    my $path = "/run/qemu-server/$vmid.reboot";
+    my $res = 0;
+
+    $res = unlink($path);
+    die "could not remove reboot request for $vmid: $!"
+       if !$res && $! != POSIX::ENOENT;
+
+    return $res;
 }
 
 # bash completion helper
@@ -7433,4 +7315,22 @@ sub complete_storage {
     return $res;
 }
 
+sub complete_migration_storage {
+    my ($cmd, $param, $current_value, $all_args) = @_;
+
+    my $targetnode = @$all_args[1];
+
+    my $cfg = PVE::Storage::config();
+    my $ids = $cfg->{ids};
+
+    my $res = [];
+    foreach my $sid (keys %$ids) {
+       next if !PVE::Storage::storage_check_enabled($cfg, $sid, $targetnode, 1);
+       next if !$ids->{$sid}->{content}->{images};
+       push @$res, $sid;
+    }
+
+    return $res;
+}
+
 1;