]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
add targetstorage to vm_start
[qemu-server.git] / PVE / QemuServer.pm
index bb8a94daf94ef423be80a7ba8ec5a3f66bd7d488..e7071c07577ab9eac58f37a713b7f430eedb6a24 100644 (file)
@@ -30,10 +30,17 @@ use PVE::ProcFSTools;
 use PVE::QemuConfig;
 use PVE::QMPClient;
 use PVE::RPCEnvironment;
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
+use PVE::QemuServer::Memory;
+use PVE::QemuServer::USB qw(parse_usb_device);
 use Time::HiRes qw(gettimeofday);
 use File::Copy qw(copy);
 use URI::Escape;
 
+my $OVMF_CODE = '/usr/share/kvm/OVMF_CODE-pure-efi.fd';
+my $OVMF_VARS = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
+my $OVMF_IMG = '/usr/share/kvm/OVMF-pure-efi.fd';
+
 my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
@@ -68,6 +75,13 @@ PVE::JSONSchema::register_standard_option('pve-snapshot-name', {
     maxLength => 40,
 });
 
+PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
+    type => 'string',
+    enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
+    description => "The drive's backing file's data format.",
+    optional => 1,
+});
+
 #no warnings 'redefine';
 
 sub cgroups_write {
@@ -92,12 +106,47 @@ mkdir $lock_dir;
 
 my $pcisysfs = "/sys/bus/pci";
 
+my $cpu_vendor_list = {
+    # Intel CPUs
+    486 => 'GenuineIntel',
+    pentium => 'GenuineIntel',
+    pentium2  => 'GenuineIntel',
+    pentium3  => 'GenuineIntel',
+    coreduo => 'GenuineIntel',
+    core2duo => 'GenuineIntel',
+    Conroe  => 'GenuineIntel',
+    Penryn  => 'GenuineIntel', 
+    Nehalem  => 'GenuineIntel',
+    Westmere => 'GenuineIntel',
+    SandyBridge => 'GenuineIntel',
+    IvyBridge => 'GenuineIntel',
+    Haswell => 'GenuineIntel',
+    'Haswell-noTSX' => 'GenuineIntel',
+    Broadwell => 'GenuineIntel',
+    'Broadwell-noTSX' => 'GenuineIntel',
+    
+    # AMD CPUs
+    athlon => 'AuthenticAMD',
+    phenom  => 'AuthenticAMD',
+    Opteron_G1  => 'AuthenticAMD',
+    Opteron_G2  => 'AuthenticAMD',
+    Opteron_G3  => 'AuthenticAMD',
+    Opteron_G4  => 'AuthenticAMD',
+    Opteron_G5  => 'AuthenticAMD',
+
+    # generic types, use vendor from host node
+    host => 'default',
+    kvm32 => 'default',
+    kvm64 => 'default',
+    qemu32 => 'default',
+    qemu64 => 'default',
+};
+
 my $cpu_fmt = {
     cputype => {
        description => "Emulated CPU type.",
        type => 'string',
-       enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge IvyBridge Haswell Haswell-noTSX Broadwell Broadwell-noTSX Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ],
-       format_description => 'cputype',
+       enum => [ sort { "\L$a" cmp "\L$b" } keys %$cpu_vendor_list ],
        default => 'kvm64',
        default_key => 1,
     },
@@ -109,6 +158,24 @@ my $cpu_fmt = {
     },
 };
 
+my $watchdog_fmt = {
+    model => {
+       default_key => 1,
+       type => 'string',
+       enum => [qw(i6300esb ib700)],
+       description => "Watchdog type to emulate.",
+       default => 'i6300esb',
+       optional => 1,
+    },
+    action => {
+       type => 'string',
+       enum => [qw(reset shutdown poweroff pause debug none)],
+       description => "The action to perform if after activation the guest fails to poll the watchdog in time.",
+       optional => 1,
+    },
+};
+PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt);
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -143,18 +210,20 @@ my $confdesc = {
     cpulimit => {
        optional => 1,
        type => 'number',
-       description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
+       description => "Limit of CPU usage.",
+        verbose_description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
        minimum => 0,
        maximum => 128,
-       default => 0,
+        default => 0,
     },
     cpuunits => {
        optional => 1,
        type => 'integer',
-       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+        description => "CPU weight for a VM.",
+       verbose_description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
        minimum => 0,
        maximum => 500000,
-       default => 1000,
+       default => 1024,
     },
     memory => {
        optional => 1,
@@ -180,7 +249,7 @@ my $confdesc = {
     keyboard => {
        optional => 1,
        type => 'string',
-       description => "Keybord layout for vnc server. Default is read from the datacenter configuration file.",
+       description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.",
        enum => PVE::Tools::kvmkeymaplist(),
        default => 'en-us',
     },
@@ -192,7 +261,7 @@ my $confdesc = {
     scsihw => {
        optional => 1,
        type => 'string',
-       description => "scsi controller model",
+       description => "SCSI controller model",
        enum => [qw(lsi lsi53c810 virtio-scsi-pci virtio-scsi-single megasas pvscsi)],
        default => 'lsi',
     },
@@ -204,25 +273,24 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26 solaris)],
-       description => <<EODESC,
-Used to enable special optimization/features for specific
-operating systems:
-
-other  => unspecified OS
-wxp    => Microsoft Windows XP
-w2k    => Microsoft Windows 2000
-w2k3   => Microsoft Windows 2003
-w2k8   => Microsoft Windows 2008
-wvista => Microsoft Windows Vista
-win7   => Microsoft Windows 7
-win8   => Microsoft Windows 8/2012
-l24    => Linux 2.4 Kernel
-l26    => Linux 2.6/3.X Kernel
-solaris => solaris/opensolaris/openindiania kernel
-
-other|l24|l26|solaris                       ... no special behaviour
-wxp|w2k|w2k3|w2k8|wvista|win7|win8  ... use --localtime switch
+        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 win10 l24 l26 solaris)],
+       description => "Specify guest operating system.",
+       verbose_description => <<EODESC,
+Specify guest operating system. This is used to enable special
+optimization/features for specific operating systems:
+
+[horizontal]
+other;; unspecified OS
+wxp;; Microsoft Windows XP
+w2k;; Microsoft Windows 2000
+w2k3;; Microsoft Windows 2003
+w2k8;; Microsoft Windows 2008
+wvista;; Microsoft Windows Vista
+win7;; Microsoft Windows 7
+win8;; Microsoft Windows 8/2012
+l24;; Linux 2.4 Kernel
+l26;; Linux 2.6/3.X Kernel
+solaris;; Solaris/OpenSolaris/OpenIndiania kernel
 EODESC
     },
     boot => {
@@ -265,6 +333,12 @@ EODESC
        description => "Enable/disable NUMA.",
        default => 0,
     },
+    hugepages => {
+       optional => 1,
+       type => 'string',
+       description => "Enable/disable hugepages memory.",
+       enum => [qw(any 2 1024)],
+    },
     vcpus => {
        optional => 1,
        type => 'integer',
@@ -309,7 +383,8 @@ EODESC
     vga => {
        optional => 1,
        type => 'string',
-       description => "Select the VGA type. If you want to use high resolution" .
+       description => "Select the VGA type.",
+        verbose_description => "Select the VGA type. If you want to use high resolution" .
            " modes (>= 1280x1024x16) then you should use the options " .
            "'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and " .
            "'cirrus' for other OS types. The 'qxl' option enables the SPICE " .
@@ -322,8 +397,8 @@ EODESC
     watchdog => {
        optional => 1,
        type => 'string', format => 'pve-qm-watchdog',
-       typetext => '[[model=]i6300esb|ib700] [,[action=]reset|shutdown|poweroff|pause|debug|none]',
-       description => "Create a virtual hardware watchdog device. Once enabled" .
+       description => "Create a virtual hardware watchdog device.",
+       verbose_description => "Create a virtual hardware watchdog device. Once enabled" .
            " (by a guest action), the watchdog must be periodically polled " .
            "by an agent inside the guest or else the watchdog will reset " .
            "the guest (or execute the respective action specified)",
@@ -346,17 +421,21 @@ EODESC
     args => {
        optional => 1,
        type => 'string',
-       description => <<EODESCR,
-NOTE: this option is for experts only. It allows you to pass arbitrary arguments to kvm, for example:
+       description => "Arbitrary arguments passed to kvm.",
+       verbose_description => <<EODESCR,
+Arbitrary arguments passed to kvm, for example:
 
 args: -no-reboot -no-hpet
+
+NOTE: this option is for experts only.
 EODESCR
     },
     tablet => {
        optional => 1,
        type => 'boolean',
        default => 1,
-       description => "Enable/disable the USB tablet device. This device is " .
+       description => "Enable/disable the USB tablet device.",
+       verbose_description => "Enable/disable the USB tablet device. This device is " .
            "usually needed to allow absolute mouse positioning with VNC. " .
            "Else the mouse runs out of sync with normal VNC clients. " .
            "If you're running lots of console-only guests on one host, " .
@@ -379,8 +458,8 @@ EODESCR
     },
     cdrom => {
        optional => 1,
-       type => 'string', format => 'pve-qm-drive',
-       typetext => 'volume',
+       type => 'string', format => 'pve-qm-ide',
+       typetext => '<volume>',
        description => "This is an alias for option -ide2",
     },
     cpu => {
@@ -420,7 +499,7 @@ EODESCR
     protection => {
        optional => 1,
        type => 'boolean',
-       description => "Sets the protection flag of the VM. This will prevent the remove operation.",
+       description => "Sets the protection flag of the VM. This will disable the remove VM and remove disk operations.",
        default => 0,
     },
     bios => {
@@ -464,34 +543,30 @@ my $MAX_HOSTPCI_DEVICES = 4;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
 my $MAX_NUMA = 8;
-my $MAX_MEM = 4194304;
-my $STATICMEM = 1024;
 
 my $numa_fmt = {
     cpus => {
        type => "string",
        pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/,
-       description => "CPUs accessing this numa node.",
+       description => "CPUs accessing this NUMA node.",
        format_description => "id[-id];...",
     },
     memory => {
        type => "number",
-       description => "Amount of memory this numa node provides.",
-       format_description => "mb",
+       description => "Amount of memory this NUMA node provides.",
        optional => 1,
     },
     hostnodes => {
        type => "string",
        pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/,
-       description => "host numa nodes to use",
+       description => "Host NUMA nodes to use.",
        format_description => "id[-id];...",
        optional => 1,
     },
     policy => {
        type => 'string',
        enum => [qw(preferred bind interleave)],
-       format_description => 'preferred|bind|interleave',
-       description => "numa allocation policy.",
+       description => "NUMA allocation policy.",
        optional => 1,
     },
 };
@@ -499,7 +574,7 @@ PVE::JSONSchema::register_format('pve-qm-numanode', $numa_fmt);
 my $numadesc = {
     optional => 1,
     type => 'string', format => $numa_fmt,
-    description => "numa topology",
+    description => "NUMA topology.",
 };
 PVE::JSONSchema::register_standard_option("pve-qm-numanode", $numadesc);
 
@@ -512,43 +587,100 @@ my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000',  'pcnet',  'virtio',
                      'e1000-82540em', 'e1000-82544gc', 'e1000-82545em'];
 my $nic_model_list_txt = join(' ', sort @$nic_model_list);
 
-my $netdesc = {
-    optional => 1,
-    type => 'string', format => 'pve-qm-net',
-    typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,queues=<nbqueues>][,rate=<mbps>] [,tag=<vlanid>][,trunks=<vlanid[;vlanid]>][,firewall=0|1],link_down=0|1]",
-    description => <<EODESCR,
-Specify network devices.
-
-MODEL is one of: $nic_model_list_txt
+my $net_fmt_bridge_descr = <<__EOD__;
+Bridge to attach the network device to. The Proxmox VE standard bridge
+is called 'vmbr0'.
 
-XX:XX:XX:XX:XX:XX should be an unique MAC address. This is
-automatically generated if not specified.
+If you do not specify a bridge, we create a kvm user (NATed) network
+device, which provides DHCP and DNS services. The following addresses
+are used:
 
-The bridge parameter can be used to automatically add the interface to a bridge device. The Proxmox VE standard bridge is called 'vmbr0'.
-
-Option 'rate' is used to limit traffic bandwidth from and to this interface. It is specified as floating point number, unit is 'Megabytes per second'.
-
-If you specify no bridge, we create a kvm 'user' (NATed) network device, which provides DHCP and DNS services. The following addresses are used:
-
-10.0.2.2   Gateway
-10.0.2.3   DNS Server
-10.0.2.4   SMB Server
+ 10.0.2.2   Gateway
+ 10.0.2.3   DNS Server
+ 10.0.2.4   SMB Server
 
 The DHCP server assign addresses to the guest starting from 10.0.2.15.
+__EOD__
 
-EODESCR
+my $net_fmt = {
+    macaddr => {
+       type => 'string',
+       pattern => qr/[0-9a-f]{2}(?::[0-9a-f]{2}){5}/i,
+       description => "MAC address. That address must be unique withing your network. This is automatically generated if not specified.",
+       format_description => "XX:XX:XX:XX:XX:XX",
+       optional => 1,
+    },
+    model => {
+       type => 'string',
+       description => "Network Card Model. The 'virtio' model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use 'e1000'.",
+        enum => $nic_model_list,
+        default_key => 1,
+    },
+    (map { $_ => { keyAlias => 'model', alias => 'macaddr' }} @$nic_model_list),
+    bridge => {
+       type => 'string',
+       description => $net_fmt_bridge_descr,
+       format_description => 'bridge',
+       optional => 1,
+    },
+    queues => {
+       type => 'integer',
+       minimum => 0, maximum => 16,
+       description => 'Number of packet queues to be used on the device.',
+       optional => 1,
+    },
+    rate => {
+       type => 'number',
+       minimum => 0,
+       description => "Rate limit in mbps (megabytes per second) as floating point number.",
+       optional => 1,
+    },
+    tag => {
+       type => 'integer',
+       minimum => 1, maximum => 4094,
+       description => 'VLAN tag to apply to packets on this interface.',
+       optional => 1,
+    },
+    trunks => {
+       type => 'string',
+       pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/,
+       description => 'VLAN trunks to pass through this interface.',
+       format_description => 'vlanid[;vlanid...]',
+       optional => 1,
+    },
+    firewall => {
+       type => 'boolean',
+       description => 'Whether this interface should be protected by the firewall.',
+       optional => 1,
+    },
+    link_down => {
+       type => 'boolean',
+       description => 'Whether this interface should be disconnected (like pulling the plug).',
+       optional => 1,
+    },
+};
+
+my $netdesc = {
+    optional => 1,
+    type => 'string', format => $net_fmt,
+    description => "Specify network devices.",
 };
+
 PVE::JSONSchema::register_standard_option("pve-qm-net", $netdesc);
 
 for (my $i = 0; $i < $MAX_NETS; $i++)  {
     $confdesc->{"net$i"} = $netdesc;
 }
 
-PVE::JSONSchema::register_format('pve-volume-id-or-none', \&verify_volume_id_or_none);
-sub verify_volume_id_or_none {
+PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path);
+sub verify_volume_id_or_qm_path {
     my ($volid, $noerr) = @_;
 
-    return $volid if $volid eq 'none';
+    if ($volid eq 'none' || $volid eq 'cdrom' || $volid =~ m|^/|) {
+       return $volid;
+    }
+
+    # if its neither 'none' nor 'cdrom' nor a path, check if its a volume-id
     $volid = eval { PVE::JSONSchema::check_format('pve-volume-id', $volid, '') };
     if ($@) {
        return undef if $noerr;
@@ -563,14 +695,13 @@ my %drivedesc_base = (
     volume => { alias => 'file' },
     file => {
        type => 'string',
-       format => 'pve-volume-id-or-none',
+       format => 'pve-volume-id-or-qm-path',
        default_key => 1,
        format_description => 'volume',
        description => "The drive's backing volume.",
     },
     media => {
        type => 'string',
-       format_description => 'cdrom|disk',
        enum => [qw(cdrom disk)],
        description => "The drive's media type.",
        default => 'disk',
@@ -578,78 +709,63 @@ my %drivedesc_base = (
     },
     cyls => {
        type => 'integer',
-       format_description => 'count',
        description => "Force the drive's physical geometry to have a specific cylinder count.",
        optional => 1
     },
     heads => {
        type => 'integer',
-       format_description => 'count',
        description => "Force the drive's physical geometry to have a specific head count.",
        optional => 1
     },
     secs => {
        type => 'integer',
-       format_description => 'count',
        description => "Force the drive's physical geometry to have a specific sector count.",
        optional => 1
     },
     trans => {
        type => 'string',
-       format_description => 'none|lba|auto',
        enum => [qw(none lba auto)],
        description => "Force disk geometry bios translation mode.",
        optional => 1,
     },
     snapshot => {
        type => 'boolean',
-       format_description => 'on|off',
        description => "Whether the drive should be included when making snapshots.",
        optional => 1,
     },
     cache => {
        type => 'string',
-       format_description => 'none|writethrough|writeback|unsafe|directsync',
        enum => [qw(none writethrough writeback unsafe directsync)],
        description => "The drive's cache mode",
        optional => 1,
     },
-    format => {
-       type => 'string',
-       format_description => 'drive format',
-       enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
-       description => "The drive's backing file's data format.",
-       optional => 1,
-    },
+    format => get_standard_option('pve-qm-image-format'),
     size => {
        type => 'string',
        format => 'disk-size',
+       format_description => 'DiskSize',
        description => "Disk size. This is purely informational and has no effect.",
        optional => 1,
     },
     backup => {
        type => 'boolean',
-       format_description => 'on|off',
        description => "Whether the drive should be included when making backups.",
        optional => 1,
     },
     werror => {
        type => 'string',
-       format_description => 'enospc|ignore|report|stop',
        enum => [qw(enospc ignore report stop)],
        description => 'Write error action.',
        optional => 1,
     },
     aio => {
        type => 'string',
-       format_description => 'native|threads',
        enum => [qw(native threads)],
        description => 'AIO type to use.',
        optional => 1,
     },
     discard => {
        type => 'string',
-       format_description => 'ignore|on',
        enum => [qw(ignore on)],
        description => 'Controls whether to pass discard/trim requests to the underlying storage.',
        optional => 1,
@@ -672,7 +788,6 @@ my %drivedesc_base = (
 my %rerror_fmt = (
     rerror => {
        type => 'string',
-       format_description => 'ignore|report|stop',
        enum => [qw(ignore report stop)],
        description => 'Read error action.',
        optional => 1,
@@ -681,7 +796,6 @@ my %rerror_fmt = (
 
 my %iothread_fmt = ( iothread => {
        type => 'boolean',
-       format_description => 'off|on',
        description => "Whether to use iothreads for this drive",
        optional => 1,
 });
@@ -700,7 +814,6 @@ my %model_fmt = (
 my %queues_fmt = (
     queues => {
        type => 'integer',
-       format_description => 'nbqueues',
        description => "Number of queues.",
        minimum => 2,
        optional => 1
@@ -708,38 +821,49 @@ my %queues_fmt = (
 );
 
 my $add_throttle_desc = sub {
-    my ($key, $type, $what, $size, $longsize) = @_;
-    $drivedesc_base{$key} = {
+    my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
+    my $d = {
        type => $type,
-       format_description => $size,
-       description => "Maximum $what speed in $longsize per second.",
+       format_description => $unit,
+       description => "Maximum $what in $longunit.",
        optional => 1,
     };
+    $d->{minimum} = $minimum if defined($minimum);
+    $drivedesc_base{$key} = $d;
 };
 # throughput: (leaky bucket)
-$add_throttle_desc->('bps',     'integer', 'r/w speed',   'bps',  'bytes');
-$add_throttle_desc->('bps_rd',  'integer', 'read speed',  'bps',  'bytes');
-$add_throttle_desc->('bps_wr',  'integer', 'write speed', 'bps',  'bytes');
-$add_throttle_desc->('mbps',    'number',  'r/w speed',   'mbps', 'megabytes');
-$add_throttle_desc->('mbps_rd', 'number',  'read speed',  'mbps', 'megabytes');
-$add_throttle_desc->('mbps_wr', 'number',  'write speed', 'mbps', 'megabytes');
-$add_throttle_desc->('iops',    'integer', 'r/w I/O',     'iops', 'operations');
-$add_throttle_desc->('iops_rd', 'integer', 'read I/O',    'iops', 'operations');
-$add_throttle_desc->('iops_wr', 'integer', 'write I/O',   'iops', 'operations');
+$add_throttle_desc->('bps',     'integer', 'r/w speed',   'bps',  'bytes per second');
+$add_throttle_desc->('bps_rd',  'integer', 'read speed',  'bps',  'bytes per second');
+$add_throttle_desc->('bps_wr',  'integer', 'write speed', 'bps',  'bytes per second');
+$add_throttle_desc->('mbps',    'number',  'r/w speed',   'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_rd', 'number',  'read speed',  'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_wr', 'number',  'write speed', 'mbps', 'megabytes per second');
+$add_throttle_desc->('iops',    'integer', 'r/w I/O',     'iops', 'operations per second');
+$add_throttle_desc->('iops_rd', 'integer', 'read I/O',    'iops', 'operations per second');
+$add_throttle_desc->('iops_wr', 'integer', 'write I/O',   'iops', 'operations per second');
 
 # pools: (pool of IO before throttling starts taking effect)
-$add_throttle_desc->('mbps_max',    'number',  'unthrottled r/w pool',       'mbps', 'megabytes');
-$add_throttle_desc->('mbps_rd_max', 'number',  'unthrottled read pool',      'mbps', 'megabytes');
-$add_throttle_desc->('mbps_wr_max', 'number',  'unthrottled write pool',     'mbps', 'megabytes');
-$add_throttle_desc->('iops_max',    'integer', 'unthrottled r/w I/O pool',   'iops', 'operations');
-$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool',  'iops', 'operations');
-$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations');
+$add_throttle_desc->('mbps_max',    'number',  'unthrottled r/w pool',       'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_rd_max', 'number',  'unthrottled read pool',      'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_wr_max', 'number',  'unthrottled write pool',     'mbps', 'megabytes per second');
+$add_throttle_desc->('iops_max',    'integer', 'unthrottled r/w I/O pool',   'iops', 'operations per second');
+$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool',  'iops', 'operations per second');
+$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
+
+# burst lengths
+$add_throttle_desc->('bps_max_length',  'integer', 'length of I/O bursts',       'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_rd_length',   'integer', 'length of read I/O bursts',  'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_wr_length',   'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts',       'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_rd_length',  'integer', 'length of read I/O bursts',  'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_wr_length',  'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
 
 my $ide_fmt = {
     %drivedesc_base,
     %rerror_fmt,
     %model_fmt,
 };
+PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
 
 my $idedesc = {
     optional => 1,
@@ -791,60 +915,116 @@ my $alldrive_fmt = {
     %queues_fmt,
 };
 
+my $efidisk_fmt = {
+    volume => { alias => 'file' },
+    file => {
+       type => 'string',
+       format => 'pve-volume-id-or-qm-path',
+       default_key => 1,
+       format_description => 'volume',
+       description => "The drive's backing volume.",
+    },
+    format => get_standard_option('pve-qm-image-format'),
+    size => {
+       type => 'string',
+       format => 'disk-size',
+       format_description => 'DiskSize',
+       description => "Disk size. This is purely informational and has no effect.",
+       optional => 1,
+    },
+};
+
+my $efidisk_desc = {
+    optional => 1,
+    type => 'string', format => $efidisk_fmt,
+    description => "Configure a Disk for storing EFI vars",
+};
+
+PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
+
 my $usb_fmt = {
     host => {
        default_key => 1,
        type => 'string', format => 'pve-qm-usb-device',
        format_description => 'HOSTUSBDEVICE|spice',
-       description => 'The Host USB device or port or the value spice',
+        description => <<EODESCR,
+The Host USB device or port or the value 'spice'. HOSTUSBDEVICE syntax is:
+
+ 'bus-port(.port)*' (decimal numbers) or
+ 'vendor_id:product_id' (hexadeciaml numbers) or
+ 'spice'
+
+You can use the 'lsusb -t' command to list existing usb devices.
+
+NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+
+The value 'spice' can be used to add a usb redirection devices for spice.
+EODESCR
     },
     usb3 => {
        optional => 1,
        type => 'boolean',
-       format_description => 'yes|no',
-       description => 'Specifies whether if given host option is a USB3 device or port',
+       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).",
+        default => 0,
     },
 };
 
 my $usbdesc = {
     optional => 1,
     type => 'string', format => $usb_fmt,
-    description => <<EODESCR,
-Configure an USB device (n is 0 to 4). This can be used to
-pass-through usb devices to the guest. HOSTUSBDEVICE syntax is:
-
-'bus-port(.port)*' (decimal numbers) or
-'vendor_id:product_id' (hexadeciaml numbers) or
-'spice'
-
-You can use the 'lsusb -t' command to list existing usb devices.
-
-NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+    description => "Configure an USB device (n is 0 to 4).",
+};
+PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
 
-The value 'spice' can be used to add a usb redirection devices for spice.
+# NOTE: the match-groups of this regex are used in parse_hostpci
+my $PCIRE = qr/([a-f0-9]{2}:[a-f0-9]{2})(?:\.([a-f0-9]))?/;
+my $hostpci_fmt = {
+    host => {
+       default_key => 1,
+       type => 'string',
+       pattern => qr/$PCIRE(;$PCIRE)*/,
+       format_description => 'HOSTPCIID[;HOSTPCIID2...]',
+       description => <<EODESCR,
+Host PCI device pass through. The PCI ID of a host's PCI device or a list 
+of PCI virtual functions of the host. HOSTPCIID syntax is:
 
-The 'usb3' option determines whether the device is a USB3 device or not (this does currently not work reliably with spice redirection and is then ignored).
+'bus:dev.func' (hexadecimal numbers)
 
+You can us the 'lspci' command to list existing PCI devices.
 EODESCR
+    },
+    rombar => {
+       type => 'boolean',
+        description =>  "Specify whether or not the device's ROM will be visible in the guest's memory map.",
+       optional => 1,
+       default => 1,
+    },
+    pcie => {
+       type => 'boolean',
+        description =>  "Choose the PCI-express bus (needs the 'q35' machine model).",
+       optional => 1,
+       default => 0,
+    },
+    'x-vga' => {
+       type => 'boolean',
+        description =>  "Enable vfio-vga device support.",
+       optional => 1,
+       default => 0,
+    },
 };
-PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
+PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
 
 my $hostpcidesc = {
         optional => 1,
         type => 'string', format => 'pve-qm-hostpci',
-        typetext => "[host=]HOSTPCIDEVICE [,rombar=on|off] [,pcie=0|1] [,x-vga=on|off]",
-        description => <<EODESCR,
-Map host pci devices. HOSTPCIDEVICE syntax is:
-
-'bus:dev.func' (hexadecimal numbers)
+        description => "Map host PCI devices into guest.",
+       verbose_description =>  <<EODESCR,
+Map host PCI devices into guest.
 
-You can us the 'lspci' command to list existing pci devices.
+NOTE: This option allows direct access to host hardware. So it is no longer 
+possible to migrate such machines - use with special care.
 
-The 'rombar' option determines whether or not the device's ROM will be visible in the guest's memory map (default is 'on').
-
-NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
-
-Experimental: user reported problems with this option.
+CAUTION: Experimental! User reported problems with this option.
 EODESCR
 };
 PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
@@ -853,12 +1033,15 @@ my $serialdesc = {
        optional => 1,
        type => 'string',
        pattern => '(/dev/.+|socket)',
-       description =>  <<EODESCR,
-Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device (i.e. /dev/ttyS0), or create a unix socket on the host side (use 'qm terminal' to open a terminal connection).
+       description =>  "Create a serial device inside the VM (n is 0 to 3)",
+       verbose_description =>  <<EODESCR,
+Create a serial device inside the VM (n is 0 to 3), and pass through a
+host serial device (i.e. /dev/ttyS0), or create a unix socket on the
+host side (use 'qm terminal' to open a terminal connection).
 
 NOTE: If you pass through a host serial device, it is no longer possible to migrate such machines - use with special care.
 
-Experimental: user reported problems with this option.
+CAUTION: Experimental! User reported problems with this option.
 EODESCR
 };
 
@@ -866,12 +1049,13 @@ my $paralleldesc= {
        optional => 1,
        type => 'string',
         pattern => '/dev/parport\d+|/dev/usb/lp\d+',
-       description =>  <<EODESCR,
+       description =>  "Map host parallel devices (n is 0 to 2).",
+       verbose_description =>  <<EODESCR,
 Map host parallel devices (n is 0 to 2).
 
 NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
 
-Experimental: user reported problems with this option.
+CAUTION: Experimental! User reported problems with this option.
 EODESCR
 };
 
@@ -907,6 +1091,9 @@ for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++)  {
     $confdesc->{"virtio$i"} = $virtiodesc;
 }
 
+$drivename_hash->{efidisk0} = 1;
+$confdesc->{efidisk0} = $efidisk_desc;
+
 for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
     $confdesc->{"usb$i"} = $usbdesc;
 }
@@ -914,7 +1101,7 @@ for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
 my $unuseddesc = {
     optional => 1,
     type => 'string', format => 'pve-volume-id',
-    description => "Reference to unused volumes.",
+    description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
 };
 
 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++)  {
@@ -968,7 +1155,8 @@ sub valid_drive_names {
     return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
             (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
             (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
-            (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))));
+            (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
+            'efidisk0');
 }
 
 sub is_valid_drivename {
@@ -997,6 +1185,7 @@ sub os_list_description {
        wvista => 'Windows Vista',
        win7 => 'Windows 7',
        win8 => 'Windows 8/2012',
+       win10 => 'Windows 10/2016',
        l24 => 'Linux 2.4',
        l26 => 'Linux 2.6',
     };
@@ -1097,7 +1286,7 @@ sub parse_hotplug_features {
        if ($feature =~ m/^(network|disk|cpu|memory|usb)$/) {
            $res->{$1} = 1;
        } else {
-           warn "ignoring unknown hotplug feature '$feature'\n";
+           die "invalid hotplug feature '$feature'\n";
        }
     }
     return $res;
@@ -1154,6 +1343,22 @@ sub parse_drive {
            $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
        }
     }
+
+    # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
+    for my $requirement (
+       [bps_max_length => 'mbps_max'],
+       [bps_rd_max_length => 'mbps_rd_max'],
+       [bps_wr_max_length => 'mbps_wr_max'],
+       [iops_max_length => 'iops_max'],
+       [iops_rd_max_length => 'iops_rd_max'],
+       [iops_wr_max_length => 'iops_wr_max']) {
+       my ($option, $requires) = @$requirement;
+       if ($res->{$option} && !$res->{$requires}) {
+           warn "$option requires $requires\n";
+           ++$error;
+       }
+    }
+
     return undef if $error;
 
     return undef if $res->{mbps_rd} && $res->{mbps};
@@ -1490,6 +1695,28 @@ sub print_netdev_full {
     return $netdev;
 }
 
+
+sub print_cpu_device {
+    my ($conf, $id) = @_;
+
+    my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
+    my $cpu = $nokvm ? "qemu64" : "kvm64";
+    if (my $cputype = $conf->{cpu}) {
+       my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
+           or die "Cannot parse cpu description: $cputype\n";
+       $cpu = $cpuconf->{cputype};
+    }
+
+    my $sockets = 1;
+    $sockets = $conf->{sockets} if  $conf->{sockets};
+    my $cores = $conf->{cores} || 1;
+
+    my $current_core = ($id - 1) % $cores;
+    my $current_socket = int(($id - $current_core)/$cores);
+
+    return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
+}
+
 sub drive_is_cdrom {
     my ($drive) = @_;
 
@@ -1525,35 +1752,23 @@ sub parse_hostpci {
 
     return undef if !$value;
 
+    my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);
 
-    my @list = split(/,/, $value);
-    my $found;
-
-    my $res = {};
-    foreach my $kv (@list) {
-
-       if ($kv =~ m/^(host=)?([a-f0-9]{2}:[a-f0-9]{2})(\.([a-f0-9]))?$/) {
-           $found = 1;
-           if(defined($4)){
-               push @{$res->{pciid}}, { id => $2 , function => $4};
-
-           }else{
-               my $pcidevices = lspci($2);
-               $res->{pciid} = $pcidevices->{$2};
+    my @idlist = split(/;/, $res->{host});
+    delete $res->{host};
+    foreach my $id (@idlist) {
+       if ($id =~ /^$PCIRE$/) {
+           if (defined($2)) {
+               push @{$res->{pciid}}, { id => $1, function => $2 };
+           } else {
+               my $pcidevices = lspci($1);
+               $res->{pciid} = $pcidevices->{$1};
            }
-       } elsif ($kv =~ m/^rombar=(on|off)$/) {
-           $res->{rombar} = $1;
-       } elsif ($kv =~ m/^x-vga=(on|off)$/) {
-           $res->{'x-vga'} = $1;
-       } elsif ($kv =~ m/^pcie=(\d+)$/) {
-           $res->{pcie} = 1 if $1 == 1;
        } else {
-           warn "unknown hostpci setting '$kv'\n";
+           # should have been caught by parse_property_string already
+           die "failed to parse PCI id: $id\n";
        }
     }
-
-    return undef if !$found;
-
     return $res;
 }
 
@@ -1561,54 +1776,22 @@ sub parse_hostpci {
 sub parse_net {
     my ($data) = @_;
 
-    my $res = {};
-
-    foreach my $kvp (split(/,/, $data)) {
-
-       if ($kvp =~ m/^(ne2k_pci|e1000|e1000-82540em|e1000-82544gc|e1000-82545em|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er|vmxnet3)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
-           my $model = lc($1);
-           my $mac = defined($3) ? uc($3) : PVE::Tools::random_ether_addr();
-           $res->{model} = $model;
-           $res->{macaddr} = $mac;
-       } elsif ($kvp =~ m/^bridge=(\S+)$/) {
-           $res->{bridge} = $1;
-       } elsif ($kvp =~ m/^queues=(\d+)$/) {
-           $res->{queues} = $1;
-       } elsif ($kvp =~ m/^rate=(\d+(\.\d+)?)$/) {
-           $res->{rate} = $1;
-        } elsif ($kvp =~ m/^tag=(\d+)$/) {
-            $res->{tag} = $1;
-        } elsif ($kvp =~ m/^trunks=([0-9;]+)$/) {
-           $res->{trunks} = $1;
-        } elsif ($kvp =~ m/^firewall=([01])$/) {
-           $res->{firewall} = $1;
-       } elsif ($kvp =~ m/^link_down=([01])$/) {
-           $res->{link_down} = $1;
-       } else {
-           return undef;
-       }
-
+    my $res = eval { PVE::JSONSchema::parse_property_string($net_fmt, $data) };
+    if ($@) {
+       warn $@;
+       return undef;
+    }
+    if (!defined($res->{macaddr})) {
+       my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
+       $res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
     }
-
-    return undef if !$res->{model};
-
     return $res;
 }
 
 sub print_net {
     my $net = shift;
 
-    my $res = "$net->{model}";
-    $res .= "=$net->{macaddr}" if $net->{macaddr};
-    $res .= ",bridge=$net->{bridge}" if $net->{bridge};
-    $res .= ",rate=$net->{rate}" if $net->{rate};
-    $res .= ",tag=$net->{tag}" if $net->{tag};
-    $res .= ",trunks=$net->{trunks}" if $net->{trunks};
-    $res .= ",firewall=1" if $net->{firewall};
-    $res .= ",link_down=1" if $net->{link_down};
-    $res .= ",queues=$net->{queues}" if $net->{queues};
-
-    return $res;
+    return PVE::JSONSchema::print_property_string($net, $net_fmt);
 }
 
 sub add_random_macs {
@@ -1718,42 +1901,49 @@ my $smbios1_fmt = {
        type => 'string',
        pattern => '[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}',
        format_description => 'UUID',
+        description => "Set SMBIOS1 UUID.",
        optional => 1,
     },
     version => {
        type => 'string',
        pattern => '\S+',
-       format_description => 'str',
+       format_description => 'string',
+        description => "Set SMBIOS1 version.",
        optional => 1,
     },
     serial => {
        type => 'string',
        pattern => '\S+',
-       format_description => 'str',
+       format_description => 'string',
+        description => "Set SMBIOS1 serial number.",
        optional => 1,
     },
     manufacturer => {
        type => 'string',
        pattern => '\S+',
-       format_description => 'name',
+       format_description => 'string',
+        description => "Set SMBIOS1 manufacturer.",
        optional => 1,
     },
     product => {
        type => 'string',
        pattern => '\S+',
-       format_description => 'name',
+       format_description => 'string',
+        description => "Set SMBIOS1 product ID.",
        optional => 1,
     },
     sku => {
        type => 'string',
        pattern => '\S+',
-       format_description => 'str',
+       format_description => 'string',
+        description => "Set SMBIOS1 SKU string.",
        optional => 1,
     },
     family => {
        type => 'string',
        pattern => '\S+',
-       format_description => 'str',
+       format_description => 'string',
+        description => "Set SMBIOS1 family string.",
        optional => 1,
     },
 };
@@ -1784,79 +1974,13 @@ sub verify_bootdisk {
     die "invalid boot disk '$value'\n";
 }
 
-PVE::JSONSchema::register_format('pve-qm-net', \&verify_net);
-sub verify_net {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_net($value);
-
-    return undef if $noerr;
-
-    die "unable to parse network options\n";
-}
-
-PVE::JSONSchema::register_format('pve-qm-hostpci', \&verify_hostpci);
-sub verify_hostpci {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_hostpci($value);
-
-    return undef if $noerr;
-
-    die "unable to parse pci id\n";
-}
-
-PVE::JSONSchema::register_format('pve-qm-watchdog', \&verify_watchdog);
-sub verify_watchdog {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_watchdog($value);
-
-    return undef if $noerr;
-
-    die "unable to parse watchdog options\n";
-}
-
 sub parse_watchdog {
     my ($value) = @_;
 
     return undef if !$value;
 
-    my $res = {};
-
-    foreach my $p (split(/,/, $value)) {
-       next if $p =~ m/^\s*$/;
-
-       if ($p =~ m/^(model=)?(i6300esb|ib700)$/) {
-           $res->{model} = $2;
-       } elsif ($p =~ m/^(action=)?(reset|shutdown|poweroff|pause|debug|none)$/) {
-           $res->{action} = $2;
-       } else {
-           return undef;
-       }
-    }
-
-    return $res;
-}
-
-sub parse_usb_device {
-    my ($value) = @_;
-
-    return undef if !$value;
-
-    my $res = {};
-    if ($value =~ m/^(0x)?([0-9A-Fa-f]{4}):(0x)?([0-9A-Fa-f]{4})$/) {
-       $res->{vendorid} = $2;
-       $res->{productid} = $4;
-    } elsif ($value =~ m/^(\d+)\-(\d+(\.\d+)*)$/) {
-       $res->{hostbus} = $1;
-       $res->{hostport} = $2;
-    } elsif ($value =~ m/^spice$/i) {
-       $res->{spice} = 1;
-    } else {
-       return undef;
-    }
-
+    my $res = eval { PVE::JSONSchema::parse_property_string($watchdog_fmt, $value) };
+    warn $@ if $@;
     return $res;
 }
 
@@ -1910,12 +2034,6 @@ sub check_type {
         die "type check ('number') failed - got '$value'\n";
     } elsif ($type eq 'string') {
        if (my $fmt = $confdesc->{$key}->{format}) {
-           if ($fmt eq 'pve-qm-drive') {
-               # special case - we need to pass $key to parse_drive()
-               my $drive = parse_drive($key, $value);
-               return $value if $drive;
-               die "unable to parse drive options\n";
-           }
            PVE::JSONSchema::check_format($fmt, $value);
            return $value;
        }
@@ -1964,7 +2082,11 @@ sub destroy_vm {
        my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
        return if !$path || !$owner || ($owner != $vmid);
 
-       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) {
@@ -2063,8 +2185,9 @@ sub parse_vm_config {
            if ($@) {
                warn "vm $vmid - unable to parse value of '$key' - $@";
            } else {
+               $key = 'ide2' if $key eq 'cdrom';
                my $fmt = $confdesc->{$key}->{format};
-               if ($fmt && $fmt eq 'pve-qm-drive') {
+               if ($fmt && $fmt =~ /^pve-qm-(?:ide|scsi|virtio|sata)$/) {
                    my $v = parse_drive($key, $value);
                    if (my $volid = filename_to_volume_id($vmid, $v->{file}, $v->{media})) {
                        $v->{file} = $volid;
@@ -2075,11 +2198,7 @@ sub parse_vm_config {
                    }
                }
 
-               if ($key eq 'cdrom') {
-                   $conf->{ide2} = $value;
-               } else {
-                   $conf->{$key} = $value;
-               }
+               $conf->{$key} = $value;
            }
        }
     }
@@ -2589,7 +2708,7 @@ sub vmstatus {
        $qmpclient->queue_cmd($vmid, $statuscb, 'query-status');
     }
 
-    $qmpclient->queue_execute(undef, 1);
+    $qmpclient->queue_execute(undef, 2);
 
     foreach my $vmid (keys %$list) {
        next if $opt_vmid && ($vmid ne $opt_vmid);
@@ -2599,50 +2718,8 @@ sub vmstatus {
     return $res;
 }
 
-sub foreach_dimm {
-    my ($conf, $vmid, $memory, $sockets, $func) = @_;
-
-    my $dimm_id = 0;
-    my $current_size = 1024;
-    my $dimm_size = 512;
-    return if $current_size == $memory;
-
-    for (my $j = 0; $j < 8; $j++) {
-       for (my $i = 0; $i < 32; $i++) {
-           my $name = "dimm${dimm_id}";
-           $dimm_id++;
-           my $numanode = $i % $sockets;
-           $current_size += $dimm_size;
-           &$func($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory);
-           return  $current_size if $current_size >= $memory;
-       }
-       $dimm_size *= 2;
-    }
-}
-
-sub foreach_reverse_dimm {
-    my ($conf, $vmid, $memory, $sockets, $func) = @_;
-
-    my $dimm_id = 253;
-    my $current_size = 4177920;
-    my $dimm_size = 65536;
-    return if $current_size == $memory;
-
-    for (my $j = 0; $j < 8; $j++) {
-       for (my $i = 0; $i < 32; $i++) {
-           my $name = "dimm${dimm_id}";
-           $dimm_id--;
-           my $numanode = $i % $sockets;
-           $current_size -= $dimm_size;
-           &$func($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory);
-           return  $current_size if $current_size <= $memory;
-       }
-       $dimm_size /= 2;
-    }
-}
-
 sub foreach_drive {
-    my ($conf, $func) = @_;
+    my ($conf, $func, @param) = @_;
 
     foreach my $ds (valid_drive_names()) {
        next if !defined($conf->{$ds});
@@ -2650,12 +2727,12 @@ sub foreach_drive {
        my $drive = parse_drive($ds, $conf->{$ds});
        next if !$drive;
 
-       &$func($ds, $drive);
+       &$func($ds, $drive, @param);
     }
 }
 
 sub foreach_volid {
-    my ($conf, $func) = @_;
+    my ($conf, $func, @param) = @_;
 
     my $volhash = {};
 
@@ -2682,7 +2759,7 @@ sub foreach_volid {
     }
 
     foreach my $volid (keys %$volhash) {
-       &$func($volid, $volhash->{$volid});
+       &$func($volid, $volhash->{$volid}, @param);
     }
 }
 
@@ -2708,6 +2785,8 @@ sub config_to_command {
     my $kvmver = kvm_user_version();
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
+    my $winversion = windows_version($ostype);
+
     if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
        $vernum = $1*1000000+$2*1000;
     } elsif ($kvmver =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
@@ -2727,19 +2806,6 @@ sub config_to_command {
     my $cpuunits = defined($conf->{cpuunits}) ?
             $conf->{cpuunits} : $defaults->{cpuunits};
 
-    push @$cmd, '/usr/bin/systemd-run';
-    push @$cmd, '--scope';
-    push @$cmd, '--slice', "qemu";
-    push @$cmd, '--unit', $vmid;
-    # set KillMode=none, so that systemd don't kill those scopes
-    # at shutdown (pve-manager service should stop the VMs instead)
-    push @$cmd, '-p', "KillMode=none";
-    push @$cmd, '-p', "CPUShares=$cpuunits";
-    if ($conf->{cpulimit}) {
-       my $cpulimit = int($conf->{cpulimit} * 100);
-       push @$cmd, '-p', "CPUQuota=$cpulimit\%";
-    }
-
     push @$cmd, '/usr/bin/kvm';
 
     push @$cmd, '-id', $vmid;
@@ -2760,59 +2826,57 @@ sub config_to_command {
     }
 
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-       my $ovmfvar = "OVMF_VARS-pure-efi.fd";
-       my $ovmfvar_src = "/usr/share/kvm/$ovmfvar";
-       my $ovmfvar_dst = "/tmp/$vmid-$ovmfvar";
-       PVE::Tools::file_copy($ovmfvar_src, $ovmfvar_dst, 256*1024);
-       push @$cmd, '-drive', "if=pflash,format=raw,readonly,file=/usr/share/kvm/OVMF-pure-efi.fd";
-       push @$cmd, '-drive', "if=pflash,format=raw,file=$ovmfvar_dst";
-    }
-
-    if ($q35) {
-       # the q35 chipset support native usb2, so we enable usb controller
-       # by default for this machine type
-        push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
-    } else {
-        $pciaddr = print_pci_addr("piix3", $bridges);
-        push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2";
+       my $ovmfbase;
 
-        my $use_usb2 = 0;
-       for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
-           next if !$conf->{"usb$i"};
-           my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format},$conf->{"usb$i"}) };
-           next if !$d || $d->{usb3}; # do not add usb2 controller if we have only usb3 devices
-           $use_usb2 = 1;
+       # prefer the OVMF_CODE variant
+       if (-f $OVMF_CODE) {
+           $ovmfbase = $OVMF_CODE;
+       } elsif (-f $OVMF_IMG) {
+           $ovmfbase = $OVMF_IMG;
        }
-       # include usb device config
-       push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
-    }
-
-    # add usb3 controller if needed
 
-    my $use_usb3 = 0;
-    for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
-       next if !$conf->{"usb$i"};
-       my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format},$conf->{"usb$i"}) };
-       next if !$d || !$d->{usb3};
-       $use_usb3 = 1;
+       die "no uefi base img found\n" if !$ovmfbase;
+       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$ovmfbase";
+
+       if (defined($conf->{efidisk0}) && ($ovmfbase eq $OVMF_CODE)) {
+           my $d = PVE::JSONSchema::parse_property_string($efidisk_fmt, $conf->{efidisk0});
+           my $format = $d->{format} // 'raw';
+           my $path;
+           my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
+           if ($storeid) {
+               $path = PVE::Storage::path($storecfg, $d->{file});
+               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+               $format = qemu_img_format($scfg, $volname);
+           } else {
+               $path = $d->{file};
+               $format = "raw";
+           }
+           push @$cmd, '-drive', "if=pflash,unit=1,id=drive-efidisk0,format=$format,file=$path";
+       } elsif ($ovmfbase eq $OVMF_CODE) {
+           warn "using uefi without permanent efivars disk\n";
+           my $ovmfvar_dst = "/tmp/$vmid-ovmf.fd";
+           PVE::Tools::file_copy($OVMF_VARS, $ovmfvar_dst, 256*1024);
+           push @$cmd, '-drive', "if=pflash,unit=1,format=raw,file=$ovmfvar_dst";
+       } else {
+           # if the base img is not OVMF_CODE, we do not have to bother
+           # to create/use a vars image, since it will not be used anyway
+           # this can only happen if someone manually deletes the OVMF_CODE image
+           # or has an old pve-qemu-kvm version installed.
+           # both should not happen, but we ignore it here
+       }
     }
 
-    $pciaddr = print_pci_addr("xhci", $bridges);
-    push @$devices, '-device', "nec-usb-xhci,id=xhci$pciaddr" if $use_usb3;
 
+    # add usb controllers
+    my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $q35, $usbdesc->{format}, $MAX_USB_DEVICES);
+    push @$devices, @usbcontrollers if @usbcontrollers;
     my $vga = $conf->{vga};
 
     my $qxlnum = vga_conf_has_spice($vga);
     $vga = 'qxl' if $qxlnum;
 
     if (!$vga) {
-       if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
-                               $conf->{ostype} eq 'win7' ||
-                               $conf->{ostype} eq 'w2k8')) {
-           $vga = 'std';
-       } else {
-           $vga = 'cirrus';
-       }
+       $vga = $winversion >= 6 ? 'std' : 'cirrus';
     }
 
     # enable absolute mouse coordinates (needed by vnc)
@@ -2828,6 +2892,8 @@ sub config_to_command {
     push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
 
     my $kvm_off = 0;
+    my $gpu_passthrough;
+
     # host pci devices
     for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
        my $d = parse_hostpci($conf->{"hostpci$i"});
@@ -2841,14 +2907,14 @@ sub config_to_command {
            $pciaddr = print_pci_addr("hostpci$i", $bridges);
        }
 
-       my $rombar = $d->{rombar} && $d->{rombar} eq 'off' ? ",rombar=0" : "";
-       my $xvga = $d->{'x-vga'} && $d->{'x-vga'} eq 'on' ? ",x-vga=on" : "";
-       if ($xvga && $xvga ne '') {
+       my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
+       my $xvga = '';
+       if ($d->{'x-vga'}) {
+           $xvga = ',x-vga=on';
            $kvm_off = 1;
            $vga = 'none';
-           if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8') {
-               push @$cpuFlags , 'hv_vendor_id=proxmox';
-           }
+           $gpu_passthrough = 1;
+
            if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
                $xvga = "";
            }
@@ -2876,31 +2942,8 @@ sub config_to_command {
     }
 
     # usb devices
-    for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
-       next if !$conf->{"usb$i"};
-       my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format},$conf->{"usb$i"}) };
-       next if !$d;
-
-       # if it is a usb3 device, attach it to the xhci controller, else omit the bus option
-       my $usbbus = '';
-       if (defined($d->{usb3}) && $d->{usb3}) {
-           $usbbus = ',bus=xhci.0';
-       }
-
-       if (defined($d->{host})) {
-           $d = parse_usb_device($d->{host});
-           if (defined($d->{vendorid}) && defined($d->{productid})) {
-               push @$devices, '-device', "usb-host$usbbus,vendorid=0x$d->{vendorid},productid=0x$d->{productid}";
-           } elsif (defined($d->{hostbus}) && defined($d->{hostport})) {
-               push @$devices, '-device', "usb-host$usbbus,hostbus=$d->{hostbus},hostport=$d->{hostport}";
-           } elsif (defined($d->{spice}) && $d->{spice}) {
-               # usb redir support for spice, currently no usb3
-               push @$devices, '-chardev', "spicevmc,id=usbredirchardev$i,name=usbredir";
-               push @$devices, '-device', "usb-redir,chardev=usbredirchardev$i,id=usbredirdev$i,bus=ehci.0";
-           }
-       }
-    }
-
+    my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES);
+    push @$devices, @usbdevices if @usbdevices;
     # serial devices
     for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++)  {
        if (my $path = $conf->{"serial$i"}) {
@@ -2945,8 +2988,18 @@ sub config_to_command {
     die "MAX $allowed_vcpus vcpus allowed per VM on this node\n"
        if ($allowed_vcpus < $maxcpus);
 
-    push @$cmd, '-smp', "$vcpus,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
+    if($hotplug_features->{cpu} && qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 7)) {
 
+       push @$cmd, '-smp', "1,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
+        for (my $i = 2; $i <= $vcpus; $i++)  {
+           my $cpustr = print_cpu_device($conf,$i);
+           push @$cmd, '-device', $cpustr;
+       }
+
+    } else {
+
+       push @$cmd, '-smp', "$vcpus,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
+    }
     push @$cmd, '-nodefaults';
 
     my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
@@ -2958,7 +3011,7 @@ sub config_to_command {
        $i++;
     }
 
-    push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000";
+    push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg";
 
     push @$cmd, '-no-acpi' if defined($conf->{acpi}) && $conf->{acpi} == 0;
 
@@ -2979,35 +3032,18 @@ sub config_to_command {
     my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
     my $useLocaltime = $conf->{localtime};
 
-    if ($ostype) {
-       # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26, solaris
-
-       if ($ostype =~ m/^w/) { # windows
-           $useLocaltime = 1 if !defined($conf->{localtime});
-
-           # use time drift fix when acpi is enabled
-           if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
-               $tdf = 1 if !defined($conf->{tdf});
-           }
-       }
-
-       if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8' ||
-           $ostype eq 'wvista') {
-           push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
-           push @$cmd, '-no-hpet';
-           if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
-               push @$cpuFlags , 'hv_spinlocks=0x1fff' if !$nokvm;
-               push @$cpuFlags , 'hv_vapic' if !$nokvm;
-               push @$cpuFlags , 'hv_time' if !$nokvm;
+    if ($winversion >= 5) { # windows
+       $useLocaltime = 1 if !defined($conf->{localtime});
 
-           } else {
-               push @$cpuFlags , 'hv_spinlocks=0xffff' if !$nokvm;
-           }
+       # use time drift fix when acpi is enabled
+       if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
+           $tdf = 1 if !defined($conf->{tdf});
        }
+    }
 
-       if ($ostype eq 'win7' || $ostype eq 'win8') {
-           push @$cpuFlags , 'hv_relaxed' if !$nokvm;
-       }
+    if ($winversion >= 6) {
+       push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
+       push @$cmd, '-no-hpet';
     }
 
     push @$rtcFlags, 'driftfix=slew' if $tdf;
@@ -3047,117 +3083,28 @@ sub config_to_command {
 
     if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
 
-       push @$cpuFlags , '+kvm_pv_unhalt' if !$nokvm;
-       push @$cpuFlags , '+kvm_pv_eoi' if !$nokvm;
-    }
-
-    push @$cpuFlags, 'enforce' if $cpu ne 'host' && !$nokvm;
-
-    push @$cpuFlags, 'kvm=off' if $kvm_off;
-
-    $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
-
-    push @$cmd, '-cpu', $cpu;
-
-    my $memory = $conf->{memory} || $defaults->{memory};
-    my $static_memory = 0;
-    my $dimm_memory = 0;
-
-    if ($hotplug_features->{memory}) {
-       die "Numa need to be enabled for memory hotplug\n" if !$conf->{numa};
-       die "Total memory is bigger than ${MAX_MEM}MB\n" if $memory > $MAX_MEM;
-       $static_memory = $STATICMEM;
-       die "minimum memory must be ${static_memory}MB\n" if($memory < $static_memory);
-       $dimm_memory = $memory - $static_memory;
-       push @$cmd, '-m', "size=${static_memory},slots=255,maxmem=${MAX_MEM}M";
-
-    } else {
-
-       $static_memory = $memory;
-       push @$cmd, '-m', $static_memory;
-    }
-
-    if ($conf->{numa}) {
-
-       my $numa_totalmemory = undef;
-       for (my $i = 0; $i < $MAX_NUMA; $i++) {
-           next if !$conf->{"numa$i"};
-           my $numa = parse_numa($conf->{"numa$i"});
-           next if !$numa;
-           # memory
-           die "missing numa node$i memory value\n" if !$numa->{memory};
-           my $numa_memory = $numa->{memory};
-           $numa_totalmemory += $numa_memory;
-           my $numa_object = "memory-backend-ram,id=ram-node$i,size=${numa_memory}M";
-
-           # cpus
-           my $cpulists = $numa->{cpus};
-           die "missing numa node$i cpus\n" if !defined($cpulists);
-           my $cpus = join(',', map {
-               my ($start, $end) = @$_;
-               defined($end) ? "$start-$end" : $start
-           } @$cpulists);
-
-           # hostnodes
-           my $hostnodelists = $numa->{hostnodes};
-           if (defined($hostnodelists)) {
-               my $hostnodes;
-               foreach my $hostnoderange (@$hostnodelists) {
-                   my ($start, $end) = @$hostnoderange;
-                   $hostnodes .= ',' if $hostnodes;
-                   $hostnodes .= $start;
-                   $hostnodes .= "-$end" if defined($end);
-                   $end //= $start;
-                   for (my $i = $start; $i <= $end; ++$i ) {
-                       die "host numa node$i don't exist\n" if ! -d "/sys/devices/system/node/node$i/";
-                   }
-               }
-
-               # policy
-               my $policy = $numa->{policy};
-               die "you need to define a policy for hostnode $hostnodes\n" if !$policy;
-               $numa_object .= ",host-nodes=$hostnodes,policy=$policy";
-           }
-
-           push @$cmd, '-object', $numa_object;
-           push @$cmd, '-numa', "node,nodeid=$i,cpus=$cpus,memdev=ram-node$i";
-       }
-
-       die "total memory for NUMA nodes must be equal to vm static memory\n"
-           if $numa_totalmemory && $numa_totalmemory != $static_memory;
+       push @$cpuFlags , '+kvm_pv_unhalt' if !$nokvm;
+       push @$cpuFlags , '+kvm_pv_eoi' if !$nokvm;
+    }
 
-       #if no custom tology, we split memory and cores across numa nodes
-       if(!$numa_totalmemory) {
+    add_hyperv_enlighments($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $conf->{bios}, $gpu_passthrough);
 
-           my $numa_memory = ($static_memory / $sockets) . "M";
+    push @$cpuFlags, 'enforce' if $cpu ne 'host' && !$nokvm;
 
-           for (my $i = 0; $i < $sockets; $i++)  {
+    push @$cpuFlags, 'kvm=off' if $kvm_off;
 
-               my $cpustart = ($cores * $i);
-               my $cpuend = ($cpustart + $cores - 1) if $cores && $cores > 1;
-               my $cpus = $cpustart;
-               $cpus .= "-$cpuend" if $cpuend;
+    my $cpu_vendor = $cpu_vendor_list->{$cpu} ||
+       die "internal error"; # should not happen
 
-               push @$cmd, '-object', "memory-backend-ram,size=$numa_memory,id=ram-node$i";
-               push @$cmd, '-numa', "node,nodeid=$i,cpus=$cpus,memdev=ram-node$i";
-           }
-       }
-    }
+    push @$cpuFlags, "vendor=${cpu_vendor}"
+       if $cpu_vendor ne 'default';
 
-    if ($hotplug_features->{memory}) {
-       foreach_dimm($conf, $vmid, $memory, $sockets, sub {
-           my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;
-           push @$cmd, "-object" , "memory-backend-ram,id=mem-$name,size=${dimm_size}M";
-           push @$cmd, "-device", "pc-dimm,id=$name,memdev=mem-$name,node=$numanode";
+    $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
 
-           #if dimm_memory is not aligned to dimm map
-           if($current_size > $memory) {
-                $conf->{memory} = $current_size;
-                PVE::QemuConfig->write_config($vmid, $conf);
-           }
-       });
-    }
+    push @$cmd, '-cpu', $cpu;
 
+    PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd);
+    
     push @$cmd, '-S' if $conf->{freeze};
 
     # set keyboard layout
@@ -3181,7 +3128,7 @@ sub config_to_command {
 
     if ($qxlnum) {
        if ($qxlnum > 1) {
-           if ($conf->{ostype} && $conf->{ostype} =~ m/^w/){
+           if ($winversion){
                for(my $i = 1; $i < $qxlnum; $i++){
                    my $pciaddr = print_pci_addr("vga$i", $bridges);
                    push @$cmd, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr";
@@ -3266,6 +3213,8 @@ sub config_to_command {
            if($conf->{scsihw} && $conf->{scsihw} eq "virtio-scsi-single" && $drive->{iothread}){
                $iothread .= ",iothread=iothread-$controller_prefix$controller";
                push @$cmd, '-object', "iothread,id=iothread-$controller_prefix$controller";
+           } elsif ($drive->{iothread}) {
+               warn "iothread is only valid with virtio disk or virtio-scsi-single controller, ignoring\n";
            }
 
            my $queues = '';
@@ -3284,6 +3233,11 @@ sub config_to_command {
            $ahcicontroller->{$controller}=1;
         }
 
+       if ($drive->{interface} eq 'efidisk') {
+           # this will be added somewhere else
+           return;
+       }
+
        my $drive_cmd = print_drive_full($storecfg, $vmid, $drive);
        push @$devices, '-drive',$drive_cmd;
        push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
@@ -3400,6 +3354,16 @@ 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');
+    foreach my $per (@$resperipheral) {
+       if ($per->{name} =~ m/^usb\d+$/) {
+           $devices->{$per->{name}} = 1;
+       }
+    }
+
     return $devices;
 }
 
@@ -3417,6 +3381,14 @@ sub vm_deviceplug {
 
        qemu_deviceadd($vmid, print_tabletdevice_full($conf));
 
+    } elsif ($deviceid =~ m/^usb(\d+)$/) {
+
+       die "usb hotplug currently not reliable\n";
+       # since we can't reliably hot unplug all added usb devices
+       # and usb passthrough disables live migration
+       # we disable usb hotplugging for now
+       qemu_deviceadd($vmid, PVE::QemuServer::USB::print_usbdevice_full($conf, $deviceid, $device));
+
     } elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
 
        qemu_iothread_add($vmid, $deviceid, $device);
@@ -3512,6 +3484,15 @@ sub vm_deviceunplug {
 
        qemu_devicedel($vmid, $deviceid);
 
+    } elsif ($deviceid =~ m/^usb\d+$/) {
+
+       die "usb hotplug currently not reliable\n";
+       # when unplugging usb devices this way,
+       # there may be remaining usb controllers/hubs
+       # so we disable it for now
+       qemu_devicedel($vmid, $deviceid);
+       qemu_devicedelverify($vmid, $deviceid);
+
     } elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
 
         qemu_devicedel($vmid, $deviceid);
@@ -3744,9 +3725,36 @@ sub qemu_netdevdel {
     vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
 }
 
+sub qemu_usb_hotplug {
+    my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+
+    return if !$device;
+
+    # remove the old one first
+    vm_deviceunplug($vmid, $conf, $deviceid);
+
+    # check if xhci controller is necessary and available
+    if ($device->{usb3}) {
+
+       my $devicelist = vm_devices_list($vmid);
+
+       if (!$devicelist->{xhci}) {
+           my $pciaddr = print_pci_addr("xhci");
+           qemu_deviceadd($vmid, "nec-usb-xhci,id=xhci$pciaddr");
+       }
+    }
+    my $d = parse_usb_device($device->{host});
+    $d->{usb3} = $device->{usb3};
+
+    # add the new one
+    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d);
+}
+
 sub qemu_cpu_hotplug {
     my ($vmid, $conf, $vcpus) = @_;
 
+    my $machine_type = PVE::QemuServer::get_current_qemu_machine($vmid);
+
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
     $sockets = $conf->{sockets} if  $conf->{sockets};
@@ -3759,108 +3767,70 @@ sub qemu_cpu_hotplug {
        if $vcpus > $maxcpus;
 
     my $currentvcpus = $conf->{vcpus} || $maxcpus;
-    die "online cpu unplug is not yet possible\n"
-       if $vcpus < $currentvcpus;
-
-    my $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
-    die "vcpus in running vm is different than configuration\n"
-       if scalar(@{$currentrunningvcpus}) != $currentvcpus;
-
-    for (my $i = $currentvcpus; $i < $vcpus; $i++) {
-       vm_mon_cmd($vmid, "cpu-add", id => int($i));
-    }
-}
-
-sub qemu_memory_hotplug {
-    my ($vmid, $conf, $defaults, $opt, $value) = @_;
-
-    return $value if !check_running($vmid);
-
-    my $memory = $conf->{memory} || $defaults->{memory};
-    $value = $defaults->{memory} if !$value;
-    return $value if $value == $memory;
-
-    my $static_memory = $STATICMEM;
-    my $dimm_memory = $memory - $static_memory;
-
-    die "memory can't be lower than $static_memory MB" if $value < $static_memory;
-    die "you cannot add more memory than $MAX_MEM MB!\n" if $memory > $MAX_MEM;
-
-
-    my $sockets = 1;
-    $sockets = $conf->{sockets} if $conf->{sockets};
-
-    if($value > $memory) {
-
-       foreach_dimm($conf, $vmid, $value, $sockets, sub {
-           my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;
-
-               return if $current_size <= $conf->{memory};
-
-               eval { vm_mon_cmd($vmid, "object-add", 'qom-type' => "memory-backend-ram", id => "mem-$name", props => { size => int($dimm_size*1024*1024) } ) };
-               if (my $err = $@) {
-                   eval { qemu_objectdel($vmid, "mem-$name"); };
-                   die $err;
-               }
-
-               eval { vm_mon_cmd($vmid, "device_add", driver => "pc-dimm", id => "$name", memdev => "mem-$name", node => $numanode) };
-               if (my $err = $@) {
-                   eval { qemu_objectdel($vmid, "mem-$name"); };
-                   die $err;
-               }
-               #update conf after each succesful module hotplug
-               $conf->{memory} = $current_size;
-               PVE::QemuConfig->write_config($vmid, $conf);
-       });
-
-    } else {
 
-       foreach_reverse_dimm($conf, $vmid, $value, $sockets, sub {
-           my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;
+    if ($vcpus < $currentvcpus) {
 
-               return if $current_size >= $conf->{memory};
-               print "try to unplug memory dimm $name\n";
+       if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
 
+           for (my $i = $currentvcpus; $i > $vcpus; $i--) {
+               qemu_devicedel($vmid, "cpu$i");
                my $retry = 0;
-               while (1) {
-                   eval { qemu_devicedel($vmid, $name) };
-                   sleep 3;
-                   my $dimm_list = qemu_dimm_list($vmid);
-                   last if !$dimm_list->{$name};
-                   raise_param_exc({ $name => "error unplug memory module" }) if $retry > 5;
+               my $currentrunningvcpus = undef;
+               while (1) {
+                   $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+                   last if scalar(@{$currentrunningvcpus}) == $i-1;
+                   raise_param_exc({ vcpus => "error unplugging cpu$i" }) if $retry > 5;
                    $retry++;
+                   sleep 1;
                }
-
-               #update conf after each succesful module unplug
-               $conf->{memory} = $current_size;
-
-               eval { qemu_objectdel($vmid, "mem-$name"); };
+               #update conf after each succesfull cpu unplug
+               $conf->{vcpus} = scalar(@{$currentrunningvcpus});
                PVE::QemuConfig->write_config($vmid, $conf);
-       });
+           }
+       } else {
+           die "cpu hot-unplugging requires qemu version 2.7 or higher\n";
+       }
+
+       return;
     }
-}
 
-sub qemu_dimm_list {
-    my ($vmid) = @_;
+    my $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+    die "vcpus in running vm does not match its configuration\n"
+       if scalar(@{$currentrunningvcpus}) != $currentvcpus;
 
-    my $dimmarray = vm_mon_cmd_nocheck($vmid, "query-memory-devices");
-    my $dimms = {};
+    if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
 
-    foreach my $dimm (@$dimmarray) {
+       for (my $i = $currentvcpus+1; $i <= $vcpus; $i++) {
+           my $cpustr = print_cpu_device($conf, $i);
+           qemu_deviceadd($vmid, $cpustr);
+
+           my $retry = 0;
+           my $currentrunningvcpus = undef;
+           while (1) {
+               $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+               last if scalar(@{$currentrunningvcpus}) == $i;
+               raise_param_exc({ vcpus => "error hotplugging cpu$i" }) if $retry > 10;
+               sleep 1;
+               $retry++;
+           }
+            #update conf after each succesfull cpu hotplug
+           $conf->{vcpus} = scalar(@{$currentrunningvcpus});
+           PVE::QemuConfig->write_config($vmid, $conf);
+       }
+    } else {
 
-        $dimms->{$dimm->{data}->{id}}->{id} = $dimm->{data}->{id};
-        $dimms->{$dimm->{data}->{id}}->{node} = $dimm->{data}->{node};
-        $dimms->{$dimm->{data}->{id}}->{addr} = $dimm->{data}->{addr};
-        $dimms->{$dimm->{data}->{id}}->{size} = $dimm->{data}->{size};
-        $dimms->{$dimm->{data}->{id}}->{slot} = $dimm->{data}->{slot};
+       for (my $i = $currentvcpus; $i < $vcpus; $i++) {
+           vm_mon_cmd($vmid, "cpu-add", id => int($i));
+       }
     }
-    return $dimms;
 }
 
 sub qemu_block_set_io_throttle {
     my ($vmid, $deviceid,
        $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr,
-       $bps_max, $bps_rd_max, $bps_wr_max, $iops_max, $iops_rd_max, $iops_wr_max) = @_;
+       $bps_max, $bps_rd_max, $bps_wr_max, $iops_max, $iops_rd_max, $iops_wr_max,
+       $bps_max_length, $bps_rd_max_length, $bps_wr_max_length,
+       $iops_max_length, $iops_rd_max_length, $iops_wr_max_length) = @_;
 
     return if !check_running($vmid) ;
 
@@ -3876,7 +3846,13 @@ sub qemu_block_set_io_throttle {
        bps_wr_max => int($bps_wr_max),
        iops_max => int($iops_max),
        iops_rd_max => int($iops_rd_max),
-       iops_wr_max => int($iops_wr_max)
+       iops_wr_max => int($iops_wr_max),
+       bps_max_length => int($bps_max_length),
+       bps_rd_max_length => int($bps_rd_max_length),
+       bps_wr_max_length => int($bps_wr_max_length),
+       iops_max_length => int($iops_max_length),
+       iops_rd_max_length => int($iops_rd_max_length),
+       iops_wr_max_length => int($iops_wr_max_length),
     );
 
 }
@@ -4019,11 +3995,11 @@ sub qemu_volume_snapshot_delete {
 
     my $running = check_running($vmid);
 
-    return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
-
-    return if !$running;
-
-    vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+    if ($running && do_snapshots_with_qemu($storecfg, $volid)){
+       vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+    } else {
+       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
+    }
 }
 
 sub set_migration_caps {
@@ -4058,6 +4034,7 @@ my $fast_plug_option = {
     'shares' => 1,
     'startup' => 1,
     'description' => 1,
+    'protection' => 1,
 };
 
 # hotplug changes in [PENDING]
@@ -4107,6 +4084,12 @@ sub vmconfig_hotplug_pending {
                } else {
                    vm_deviceunplug($vmid, $conf, $opt);
                }
+           } elsif ($opt =~ m/^usb\d+/) {
+               die "skip\n";
+               # since we cannot reliably hot unplug usb devices
+               # we are disabling it
+               die "skip\n" if !$hotplug_features->{usb} || $conf->{$opt} =~ m/spice/i;
+               vm_deviceunplug($vmid, $conf, $opt);
            } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug_features->{cpu};
                qemu_cpu_hotplug($vmid, $conf, undef);
@@ -4124,7 +4107,7 @@ sub vmconfig_hotplug_pending {
                vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
            } elsif ($opt =~ m/^memory$/) {
                die "skip\n" if !$hotplug_features->{memory};
-               qemu_memory_hotplug($vmid, $conf, $defaults, $opt);
+               PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt);
            } elsif ($opt eq 'cpuunits') {
                cgroups_write("cpu", $vmid, "cpu.shares", $defaults->{cpuunits});
            } elsif ($opt eq 'cpulimit') {
@@ -4157,6 +4140,14 @@ sub vmconfig_hotplug_pending {
                } elsif ($value == 0) {
                    vm_deviceunplug($vmid, $conf, $opt);
                }
+           } elsif ($opt =~ m/^usb\d+$/) {
+               die "skip\n";
+               # since we cannot reliably hot unplug usb devices
+               # we are disabling it
+               die "skip\n" if !$hotplug_features->{usb} || $value =~ m/spice/i;
+               my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format}, $value) };
+               die "skip\n" if !$d;
+               qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d);
            } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug_features->{cpu};
                qemu_cpu_hotplug($vmid, $conf, $value);
@@ -4181,7 +4172,7 @@ sub vmconfig_hotplug_pending {
                                     $vmid, $opt, $value, 1);
            } elsif ($opt =~ m/^memory$/) { #dimms
                die "skip\n" if !$hotplug_features->{memory};
-               $value = qemu_memory_hotplug($vmid, $conf, $defaults, $opt, $value);
+               $value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt, $value);
            } elsif ($opt eq 'cpuunits') {
                cgroups_write("cpu", $vmid, "cpu.shares", $conf->{pending}->{$opt});
            } elsif ($opt eq 'cpulimit') {
@@ -4383,7 +4374,7 @@ sub vmconfig_update_disk {
                    # update existing disk
 
                    # skip non hotpluggable value
-                   if (&$safe_num_ne($drive->{discard}, $old_drive->{discard}) ||
+                   if (&$safe_string_ne($drive->{discard}, $old_drive->{discard}) ||
                        &$safe_string_ne($drive->{iothread}, $old_drive->{iothread}) ||
                        &$safe_string_ne($drive->{queues}, $old_drive->{queues}) ||
                        &$safe_string_ne($drive->{cache}, $old_drive->{cache})) {
@@ -4402,7 +4393,13 @@ sub vmconfig_update_disk {
                        &$safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
                        &$safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
                        &$safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
-                       &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max})) {
+                       &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max}) ||
+                       &$safe_num_ne($drive->{bps_max_length}, $old_drive->{bps_max_length}) ||
+                       &$safe_num_ne($drive->{bps_rd_max_length}, $old_drive->{bps_rd_max_length}) ||
+                       &$safe_num_ne($drive->{bps_wr_max_length}, $old_drive->{bps_wr_max_length}) ||
+                       &$safe_num_ne($drive->{iops_max_length}, $old_drive->{iops_max_length}) ||
+                       &$safe_num_ne($drive->{iops_rd_max_length}, $old_drive->{iops_rd_max_length}) ||
+                       &$safe_num_ne($drive->{iops_wr_max_length}, $old_drive->{iops_wr_max_length})) {
 
                        qemu_block_set_io_throttle($vmid,"drive-$opt",
                                                   ($drive->{mbps} || 0)*1024*1024,
@@ -4416,7 +4413,13 @@ sub vmconfig_update_disk {
                                                   ($drive->{mbps_wr_max} || 0)*1024*1024,
                                                   $drive->{iops_max} || 0,
                                                   $drive->{iops_rd_max} || 0,
-                                                  $drive->{iops_wr_max} || 0);
+                                                  $drive->{iops_wr_max} || 0,
+                                                  $drive->{bps_max_length} || 1,
+                                                  $drive->{bps_rd_max_length} || 1,
+                                                  $drive->{bps_wr_max_length} || 1,
+                                                  $drive->{iops_max_length} || 1,
+                                                  $drive->{iops_rd_max_length} || 1,
+                                                  $drive->{iops_wr_max_length} || 1);
 
                    }
 
@@ -4446,7 +4449,7 @@ sub vmconfig_update_disk {
 
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
-       $forcemachine, $spice_ticket) = @_;
+       $forcemachine, $spice_ticket, $migration_network, $migration_type, $targetstorage) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
        my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
@@ -4467,6 +4470,55 @@ sub vm_start {
        # set environment variable useful inside network script
        $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
 
+       my $local_volumes = {};
+
+       if( $targetstorage ){
+
+           foreach_drive($conf, sub {
+               my ($ds, $drive) = @_;
+
+               return if drive_is_cdrom($drive);
+
+               my $volid = $drive->{file};
+
+               return if !$volid;
+
+               my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
+
+               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+               return if $scfg->{shared};
+               $local_volumes->{$ds} = [$volid, $storeid, $volname];
+           });
+
+           my $format = undef;
+
+           foreach my $opt (sort keys %$local_volumes) {
+
+               my ($volid, $storeid, $volname) = @{$local_volumes->{$opt}};
+               my $drive = parse_drive($opt, $conf->{$opt});
+
+               #if remote storage is specified, use default format
+               if ($targetstorage && $targetstorage ne "1") {
+                   $storeid = $targetstorage;
+                   my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+                   $format = $defFormat;
+               } else {
+                   #else we use same format than original
+                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+                   $format = qemu_img_format($scfg, $volid);
+               }
+
+               my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, ($drive->{size}/1024));
+               my $newdrive = $drive;
+               $newdrive->{format} = $format;
+               $newdrive->{file} = $newvolid;
+               my $drivestr = PVE::QemuServer::print_drive($vmid, $newdrive);
+               $local_volumes->{$opt} = $drivestr;
+               #pass drive to conf for command line
+               $conf->{$opt} = $drivestr;
+           }
+       }
+
        my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
        my $migrate_port = 0;
@@ -4476,15 +4528,44 @@ sub vm_start {
                my $localip = "localhost";
                my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
                my $nodename = PVE::INotify::nodename();
-               if ($datacenterconf->{migration_unsecure}) {
+
+               if (!defined($migration_type)) {
+                   if (defined($datacenterconf->{migration}->{type})) {
+                       $migration_type = $datacenterconf->{migration}->{type};
+                   } else {
+                       $migration_type = 'secure';
+                   }
+               }
+
+               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 = "[$localip]" if Net::IP::ip_is_ipv6($localip);
+                   }
+
+                   $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);
                $migrate_uri = "tcp:${localip}:${migrate_port}";
                push @$cmd, '-incoming', $migrate_uri;
                push @$cmd, '-S';
+
+           } elsif ($statefile eq 'unix') {
+               # should be default for secure migrations as a ssh TCP forward
+               # tunnel is not deterministic reliable ready and fails regurarly
+               # to set up in time, so use UNIX socket forwards
+               my $socket_addr = "/run/qemu-server/$vmid.migrate";
+               unlink $socket_addr;
+
+               $migrate_uri = "unix:$socket_addr";
+
+               push @$cmd, '-incoming', $migrate_uri;
+               push @$cmd, '-S';
+
            } else {
                push @$cmd, '-loadstate', $statefile;
            }
@@ -4510,8 +4591,57 @@ sub vm_start {
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       eval  { run_command($cmd, timeout => $statefile ? undef : 30,
-                   umask => 0077); };
+       if (!check_running($vmid, 1) && -d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
+           my $cmd = [];
+           push @$cmd, '/bin/systemctl', 'stop', "$vmid.scope";
+           eval  { run_command($cmd); };
+       }
+
+       my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
+                                                 : $defaults->{cpuunits};
+
+       my %run_params = (timeout => $statefile ? undef : 30, umask => 0077);
+
+       my %properties = (
+           Slice => 'qemu.slice',
+           KillMode => 'none',
+           CPUShares => $cpuunits
+       );
+
+       if (my $cpulimit = $conf->{cpulimit}) {
+           $properties{CPUQuota} = int($cpulimit * 100);
+       }
+       $properties{timeout} = 10 if $statefile; # setting up the scope shoul be quick
+
+       if ($conf->{hugepages}) {
+
+           my $code = sub {
+               my $hugepages_topology = PVE::QemuServer::Memory::hugepages_topology($conf);
+               my $hugepages_host_topology = PVE::QemuServer::Memory::hugepages_host_topology();
+
+               PVE::QemuServer::Memory::hugepages_mount();
+               PVE::QemuServer::Memory::hugepages_allocate($hugepages_topology, $hugepages_host_topology);
+
+               eval  {
+                   PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
+                   run_command($cmd, %run_params);
+               };
+
+               if (my $err = $@) {
+                   PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology);
+                   die $err;
+               }
+
+               PVE::QemuServer::Memory::hugepages_pre_deallocate($hugepages_topology);
+           };
+           eval { PVE::QemuServer::Memory::hugepages_update_locked($code); };
+
+       } else {
+           eval  {
+               PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
+               run_command($cmd, %run_params);
+           };
+       }
 
        if (my $err = $@) {
            # deactivate volumes if start fails
@@ -4526,6 +4656,27 @@ sub vm_start {
            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 $pfamily = PVE::Tools::get_host_address_family($nodename);
+           $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}" } } );
+
+           $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";
+               print "storage migration listens on $migrate_storage_uri volume:$volid\n";
+           }
+       }
+
        if ($migratedfrom) {
 
            eval {
@@ -4633,7 +4784,7 @@ sub vm_commandline {
 
     my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
 
-    return join(' ', @$cmd);
+    return PVE::Tools::cmd2string($cmd);
 }
 
 sub vm_reset {
@@ -4956,144 +5107,6 @@ sub pci_dev_group_bind_to_vfio {
     return 1;
 }
 
-sub print_pci_addr {
-    my ($id, $bridges) = @_;
-
-    my $res = '';
-    my $devices = {
-       piix3 => { bus => 0, addr => 1 },
-       #addr2 : first videocard
-       balloon0 => { bus => 0, addr => 3 },
-       watchdog => { bus => 0, addr => 4 },
-       scsihw0 => { bus => 0, addr => 5 },
-       'pci.3' => { bus => 0, addr => 5 }, #can also be used for virtio-scsi-single bridge
-       scsihw1 => { bus => 0, addr => 6 },
-       ahci0 => { bus => 0, addr => 7 },
-       qga0 => { bus => 0, addr => 8 },
-       spice => { bus => 0, addr => 9 },
-       virtio0 => { bus => 0, addr => 10 },
-       virtio1 => { bus => 0, addr => 11 },
-       virtio2 => { bus => 0, addr => 12 },
-       virtio3 => { bus => 0, addr => 13 },
-       virtio4 => { bus => 0, addr => 14 },
-       virtio5 => { bus => 0, addr => 15 },
-       hostpci0 => { bus => 0, addr => 16 },
-       hostpci1 => { bus => 0, addr => 17 },
-       net0 => { bus => 0, addr => 18 },
-       net1 => { bus => 0, addr => 19 },
-       net2 => { bus => 0, addr => 20 },
-       net3 => { bus => 0, addr => 21 },
-       net4 => { bus => 0, addr => 22 },
-       net5 => { bus => 0, addr => 23 },
-       vga1 => { bus => 0, addr => 24 },
-       vga2 => { bus => 0, addr => 25 },
-       vga3 => { bus => 0, addr => 26 },
-       hostpci2 => { bus => 0, addr => 27 },
-       hostpci3 => { bus => 0, addr => 28 },
-       #addr29 : usb-host (pve-usb.cfg)
-       'pci.1' => { bus => 0, addr => 30 },
-       'pci.2' => { bus => 0, addr => 31 },
-       'net6' => { bus => 1, addr => 1 },
-       'net7' => { bus => 1, addr => 2 },
-       'net8' => { bus => 1, addr => 3 },
-       'net9' => { bus => 1, addr => 4 },
-       'net10' => { bus => 1, addr => 5 },
-       'net11' => { bus => 1, addr => 6 },
-       'net12' => { bus => 1, addr => 7 },
-       'net13' => { bus => 1, addr => 8 },
-       'net14' => { bus => 1, addr => 9 },
-       'net15' => { bus => 1, addr => 10 },
-       'net16' => { bus => 1, addr => 11 },
-       'net17' => { bus => 1, addr => 12 },
-       'net18' => { bus => 1, addr => 13 },
-       'net19' => { bus => 1, addr => 14 },
-       'net20' => { bus => 1, addr => 15 },
-       'net21' => { bus => 1, addr => 16 },
-       'net22' => { bus => 1, addr => 17 },
-       'net23' => { bus => 1, addr => 18 },
-       'net24' => { bus => 1, addr => 19 },
-       'net25' => { bus => 1, addr => 20 },
-       'net26' => { bus => 1, addr => 21 },
-       'net27' => { bus => 1, addr => 22 },
-       'net28' => { bus => 1, addr => 23 },
-       'net29' => { bus => 1, addr => 24 },
-       'net30' => { bus => 1, addr => 25 },
-       'net31' => { bus => 1, addr => 26 },
-       'xhci' => { bus => 1, addr => 27 },
-       'virtio6' => { bus => 2, addr => 1 },
-       'virtio7' => { bus => 2, addr => 2 },
-       'virtio8' => { bus => 2, addr => 3 },
-       'virtio9' => { bus => 2, addr => 4 },
-       'virtio10' => { bus => 2, addr => 5 },
-       'virtio11' => { bus => 2, addr => 6 },
-       'virtio12' => { bus => 2, addr => 7 },
-       'virtio13' => { bus => 2, addr => 8 },
-       'virtio14' => { bus => 2, addr => 9 },
-       'virtio15' => { bus => 2, addr => 10 },
-       'virtioscsi0' => { bus => 3, addr => 1 },
-       'virtioscsi1' => { bus => 3, addr => 2 },
-       'virtioscsi2' => { bus => 3, addr => 3 },
-       'virtioscsi3' => { bus => 3, addr => 4 },
-       'virtioscsi4' => { bus => 3, addr => 5 },
-       'virtioscsi5' => { bus => 3, addr => 6 },
-       'virtioscsi6' => { bus => 3, addr => 7 },
-       'virtioscsi7' => { bus => 3, addr => 8 },
-       'virtioscsi8' => { bus => 3, addr => 9 },
-       'virtioscsi9' => { bus => 3, addr => 10 },
-       'virtioscsi10' => { bus => 3, addr => 11 },
-       'virtioscsi11' => { bus => 3, addr => 12 },
-       'virtioscsi12' => { bus => 3, addr => 13 },
-       'virtioscsi13' => { bus => 3, addr => 14 },
-       'virtioscsi14' => { bus => 3, addr => 15 },
-       'virtioscsi15' => { bus => 3, addr => 16 },
-       'virtioscsi16' => { bus => 3, addr => 17 },
-       'virtioscsi17' => { bus => 3, addr => 18 },
-       'virtioscsi18' => { bus => 3, addr => 19 },
-       'virtioscsi19' => { bus => 3, addr => 20 },
-       'virtioscsi20' => { bus => 3, addr => 21 },
-       'virtioscsi21' => { bus => 3, addr => 22 },
-       'virtioscsi22' => { bus => 3, addr => 23 },
-       'virtioscsi23' => { bus => 3, addr => 24 },
-       'virtioscsi24' => { bus => 3, addr => 25 },
-       'virtioscsi25' => { bus => 3, addr => 26 },
-       'virtioscsi26' => { bus => 3, addr => 27 },
-       'virtioscsi27' => { bus => 3, addr => 28 },
-       'virtioscsi28' => { bus => 3, addr => 29 },
-       'virtioscsi29' => { bus => 3, addr => 30 },
-       'virtioscsi30' => { bus => 3, addr => 31 },
-
-    };
-
-    if (defined($devices->{$id}->{bus}) && defined($devices->{$id}->{addr})) {
-          my $addr = sprintf("0x%x", $devices->{$id}->{addr});
-          my $bus = $devices->{$id}->{bus};
-          $res = ",bus=pci.$bus,addr=$addr";
-          $bridges->{$bus} = 1 if $bridges;
-    }
-    return $res;
-
-}
-
-sub print_pcie_addr {
-    my ($id) = @_;
-
-    my $res = '';
-    my $devices = {
-       hostpci0 => { bus => "ich9-pcie-port-1", addr => 0 },
-       hostpci1 => { bus => "ich9-pcie-port-2", addr => 0 },
-       hostpci2 => { bus => "ich9-pcie-port-3", addr => 0 },
-       hostpci3 => { bus => "ich9-pcie-port-4", addr => 0 },
-    };
-
-    if (defined($devices->{$id}->{bus}) && defined($devices->{$id}->{addr})) {
-          my $addr = sprintf("0x%x", $devices->{$id}->{addr});
-          my $bus = $devices->{$id}->{bus};
-          $res = ",bus=$bus,addr=$addr";
-    }
-    return $res;
-
-}
-
 # vzdump restore implementaion
 
 sub tar_archive_read_firstfile {
@@ -5102,11 +5115,11 @@ sub tar_archive_read_firstfile {
     die "ERROR: file '$archive' does not exist\n" if ! -f $archive;
 
     # try to detect archive type first
-    my $pid = open (TMP, "tar tf '$archive'|") ||
+    my $pid = open (my $fh, '-|', 'tar', 'tf', $archive) ||
        die "unable to open file '$archive'\n";
-    my $firstfile = <TMP>;
+    my $firstfile = <$fh>;
     kill 15, $pid;
-    close TMP;
+    close $fh;
 
     die "ERROR: archive contaions no data\n" if !$firstfile;
     chomp $firstfile;
@@ -5184,12 +5197,13 @@ sub restore_update_config_line {
     return if $line =~ m/^parent:/;
     return if $line =~ m/^template:/; # restored VM is never a template
 
+    my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
     if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
        # try to convert old 1.X settings
        my ($id, $ind, $ethcfg) = ($1, $2, $3);
        foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {
            my ($model, $macaddr) = split(/\=/, $devconfig);
-           $macaddr = PVE::Tools::random_ether_addr() if !$macaddr || $unique;
+           $macaddr = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if !$macaddr || $unique;
            my $net = {
                model => $model,
                bridge => "vmbr$ind",
@@ -5203,10 +5217,10 @@ sub restore_update_config_line {
     } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && $unique) {
        my ($id, $netstr) = ($1, $2);
        my $net = parse_net($netstr);
-       $net->{macaddr} = PVE::Tools::random_ether_addr() if $net->{macaddr};
+       $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if $net->{macaddr};
        $netstr = print_net($net);
        print $outfd "$id: $netstr\n";
-    } elsif ($line =~ m/^((ide|scsi|virtio|sata)\d+):\s*(\S+)\s*$/) {
+    } elsif ($line =~ m/^((ide|scsi|virtio|sata|efidisk)\d+):\s*(\S+)\s*$/) {
        my $virtdev = $1;
        my $value = $3;
        my $di = parse_drive($virtdev, $value);
@@ -5508,7 +5522,10 @@ sub restore_vma_archive {
                # Note: only delete disk we want to restore
                # other volumes will become unused
                if ($virtdev_hash->{$ds}) {
-                   PVE::Storage::vdisk_free($cfg, $volid);
+                   eval { PVE::Storage::vdisk_free($cfg, $volid); };
+                   if (my $err = $@) {
+                       warn $err;
+                   }
                }
            });
 
@@ -5549,7 +5566,7 @@ sub restore_vma_archive {
                $write_zeros = 0;
            }
 
-           print $fifofh "${write_zeros}:$d->{devname}=$path\n";
+           print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
 
            print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
            $map->{$virtdev} = $volid;
@@ -5852,7 +5869,7 @@ sub qemu_img_convert {
        my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
 
        my $cmd = [];
-       push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-n';
+       push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
        push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
        push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
        if ($is_zero_initialized) {
@@ -5890,84 +5907,165 @@ sub qemu_img_format {
 }
 
 sub qemu_drive_mirror {
-    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized) = @_;
+    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete) = @_;
 
-    my $storecfg = PVE::Storage::config();
-    my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
+    $jobs = {} if !$jobs;
+
+    my $qemu_target;
+    my $format;
+
+    if($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+)/) {
+       $qemu_target = $dst_volid;
+       my $server = $1;
+       my $port = $2;
+       $format = "nbd";
+       die "can't connect remote nbd server $server:$port" if !PVE::Network::tcp_ping($server, $port, 2);
+    } else {
 
-    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+       my $storecfg = PVE::Storage::config();
+       my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
 
-    my $format = qemu_img_format($dst_scfg, $dst_volname);
+       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+
+       $format = qemu_img_format($dst_scfg, $dst_volname);
 
-    my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+       my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
 
-    my $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+       $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+    }
 
     my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $qemu_target };
     $opts->{format} = $format if $format;
 
-    print "drive mirror is starting (scanning bitmap) : this step can take some minutes/hours, depend of disk size and storage speed\n";
+    print "drive mirror is starting for drive-$drive\n";
+
+    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); }; #if a job already run for this device,it's throw an error
+    if (my $err = $@) {
+       eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
+       die "mirroring error: $err";
+    }
+
+    $jobs->{"drive-$drive"} = {};
+
+    qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete);
+}
+
+sub qemu_drive_mirror_monitor {
+    my ($vmid, $vmiddst, $jobs, $skipcomplete) = @_;
 
     eval {
-    vm_mon_cmd($vmid, "drive-mirror", %$opts);
+
+       my $err_complete = 0;
+
        while (1) {
+           die "storage migration timed out\n" if $err_complete > 300;
+
            my $stats = vm_mon_cmd($vmid, "query-block-jobs");
-           my $stat = @$stats[0];
-           die "mirroring job seem to have die. Maybe do you have bad sectors?" if !$stat;
-           die "error job is not mirroring" if $stat->{type} ne "mirror";
 
-           my $busy = $stat->{busy};
-           my $ready = $stat->{ready};
+           my $running_mirror_jobs = {};
+           foreach my $stat (@$stats) {
+               next if $stat->{type} ne 'mirror';
+               $running_mirror_jobs->{$stat->{device}} = $stat;
+           }
+
+           my $readycounter = 0;
 
-           if (my $total = $stat->{len}) {
-               my $transferred = $stat->{offset} || 0;
-               my $remaining = $total - $transferred;
-               my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+           foreach my $job (keys %$jobs) {
 
-               print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
-           }
+               if(defined($jobs->{$job}->{complete}) && !defined($running_mirror_jobs->{$job})) {
+                   print "$job : finished\n";
+                   delete $jobs->{$job};
+                   next;
+               }
 
+               die "$job: mirroring has been cancelled. Maybe do you have bad sectors?" if !defined($running_mirror_jobs->{$job});
 
-           if ($stat->{ready} eq 'true') {
+               my $busy = $running_mirror_jobs->{$job}->{busy};
+               my $ready = $running_mirror_jobs->{$job}->{ready};
+               if (my $total = $running_mirror_jobs->{$job}->{len}) {
+                   my $transferred = $running_mirror_jobs->{$job}->{offset} || 0;
+                   my $remaining = $total - $transferred;
+                   my $percent = sprintf "%.2f", ($transferred * 100 / $total);
 
-               last if $vmiddst != $vmid;
+                   print "$job: transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+               }
 
-               # try to switch the disk if source and destination are on the same guest
-               eval { vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive") };
-               last if !$@;
-               die $@ if $@ !~ m/cannot be completed/;
+               $readycounter++ if $running_mirror_jobs->{$job}->{ready} eq 'true';
            }
-           sleep 1;
-       }
 
+           last if scalar(keys %$jobs) == 0;
 
-    };
-    my $err = $@;
+           if ($readycounter == scalar(keys %$jobs)) {
+               print "all mirroring jobs are ready \n";
+               last if $skipcomplete; #do the complete later
 
-    my $cancel_job = sub {
-       vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
-       while (1) {
-           my $stats = vm_mon_cmd($vmid, "query-block-jobs");
-           my $stat = @$stats[0];
-           last if !$stat;
+               if ($vmiddst && $vmiddst != $vmid) {
+                   # if we clone a disk for a new target vm, we don't switch the disk
+                   PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs);
+                   last;
+               } else {
+
+                   foreach my $job (keys %$jobs) {
+                       # try to switch the disk if source and destination are on the same guest
+                       print "$job : Try to complete block job\n";
+
+                       eval { vm_mon_cmd($vmid, "block-job-complete", device => $job) };
+                       if ($@ =~ m/cannot be completed/) {
+                           print "$job : block job cannot be complete. Try again \n";
+                           $err_complete++;
+                       }else {
+                           print "$job : complete ok : flushing pending writes\n";
+                           $jobs->{$job}->{complete} = 1;
+                       }
+                   }
+               }
+           }
            sleep 1;
        }
     };
+    my $err = $@;
 
     if ($err) {
-       eval { &$cancel_job(); };
+       eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
        die "mirroring error: $err";
     }
 
-    if ($vmiddst != $vmid) {
-       # if we clone a disk for a new target vm, we don't switch the disk
-       &$cancel_job(); # so we call block-job-cancel
+}
+
+sub qemu_blockjobs_cancel {
+    my ($vmid, $jobs) = @_;
+
+    foreach my $job (keys %$jobs) {
+       print "$job: try to cancel block job\n";
+       eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
+       $jobs->{$job}->{cancel} = 1;
+    }
+
+    while (1) {
+       my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+
+       my $running_jobs = {};
+       foreach my $stat (@$stats) {
+           $running_jobs->{$stat->{device}} = $stat;
+       }
+
+       foreach my $job (keys %$jobs) {
+
+           if(defined($jobs->{$job}->{cancel}) && !defined($running_jobs->{$job})) {
+               print "$job : finished\n";
+               delete $jobs->{$job};
+           }
+       }
+
+       last if scalar(keys %$jobs) == 0;
+
+       sleep 1;
     }
 }
 
 sub clone_disk {
     my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
-       $newvmid, $storage, $format, $full, $newvollist) = @_;
+       $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete) = @_;
 
     my $newvolid;
 
@@ -5976,6 +6074,7 @@ sub clone_disk {
        $newvolid = PVE::Storage::vdisk_clone($storecfg,  $drive->{file}, $newvmid, $snapname);
        push @$newvollist, $newvolid;
     } else {
+
        my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
        $storeid = $storage if $storage;
 
@@ -5995,13 +6094,20 @@ sub clone_disk {
        $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $format, undef, ($size/1024));
        push @$newvollist, $newvolid;
 
-       PVE::Storage::activate_volumes($storecfg, $newvollist);
+       PVE::Storage::activate_volumes($storecfg, [$newvolid]);
 
        my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);
        if (!$running || $snapname) {
            qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
        } else {
-           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit);
+
+           my $kvmver = get_running_qemu_version ($vmid);
+           if (!qemu_machine_feature_enabled (undef, $kvmver, 2, 7)) {
+               die "drive-mirror with iothread requires qemu version 2.7 or higher\n"
+                   if $drive->{iothread};
+           }
+
+           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete);
        }
     }
 
@@ -6032,6 +6138,13 @@ sub get_current_qemu_machine {
     return $current || $default || 'pc';
 }
 
+sub get_running_qemu_version {
+    my ($vmid) = @_;
+    my $cmd = { execute => 'query-version', arguments => {} };
+    my $res = vm_qmp_command($vmid, $cmd);
+    return "$res->{qemu}->{major}.$res->{qemu}->{minor}";
+}
+
 sub qemu_machine_feature_enabled {
     my ($machine, $kvmver, $version_major, $version_minor) = @_;
 
@@ -6138,6 +6251,52 @@ sub scsihw_infos {
     return ($maxdev, $controller, $controller_prefix);
 }
 
+sub add_hyperv_enlighments {
+    my ($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $bios, $gpu_passthrough) = @_;
+
+    return if $nokvm;
+    return if $winversion < 6;
+    return if $bios && $bios eq 'ovmf' && $winversion < 8;
+
+    push @$cpuFlags , 'hv_vendor_id=proxmox' if $gpu_passthrough;
+
+    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+       push @$cpuFlags , 'hv_spinlocks=0x1fff';
+       push @$cpuFlags , 'hv_vapic';
+       push @$cpuFlags , 'hv_time';
+    } else {
+       push @$cpuFlags , 'hv_spinlocks=0xffff';
+    }
+
+    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
+       push @$cpuFlags , 'hv_reset';
+       push @$cpuFlags , 'hv_vpindex';
+       push @$cpuFlags , 'hv_runtime';
+    }
+
+    if ($winversion >= 7) {
+       push @$cpuFlags , 'hv_relaxed';
+    }
+}
+
+sub windows_version {
+    my ($ostype) = @_;
+
+    return 0 if !$ostype;
+
+    my $winversion = 0;
+
+    if($ostype eq 'wxp' || $ostype eq 'w2k3' || $ostype eq 'w2k') {
+        $winversion = 5;
+    } elsif($ostype eq 'w2k8' || $ostype eq 'wvista') {
+        $winversion = 6;
+    } elsif ($ostype =~ m/^win(\d+)$/) {
+        $winversion = $1;
+    }
+
+    return $winversion;
+}
+
 # bash completion helper
 
 sub complete_backup_archives {