]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
allow virtio-scsi + iothread controller hot-unplug
[qemu-server.git] / PVE / QemuServer.pm
index b85e505eeb140dfeb99ea3902ef48410dd86fba5..fb365849925849143247974fa8e8e2c2d4bc25ed 100644 (file)
@@ -39,7 +39,6 @@ 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};
 
@@ -56,12 +55,6 @@ cfs_register_file('/qemu-server/',
                  \&parse_vm_config,
                  \&write_vm_config);
 
-PVE::JSONSchema::register_standard_option('skiplock', {
-    description => "Ignore locks - only root is allowed to use this option.",
-    type => 'boolean',
-    optional => 1,
-});
-
 PVE::JSONSchema::register_standard_option('pve-qm-stateuri', {
     description => "Some command save/restore state from this location.",
     type => 'string',
@@ -115,16 +108,28 @@ my $cpu_vendor_list = {
     coreduo => 'GenuineIntel',
     core2duo => 'GenuineIntel',
     Conroe  => 'GenuineIntel',
-    Penryn  => 'GenuineIntel', 
+    Penryn  => 'GenuineIntel',
     Nehalem  => 'GenuineIntel',
+    'Nehalem-IBRS'  => 'GenuineIntel',
     Westmere => 'GenuineIntel',
+    'Westmere-IBRS' => 'GenuineIntel',
     SandyBridge => 'GenuineIntel',
+    'SandyBridge-IBRS' => 'GenuineIntel',
     IvyBridge => 'GenuineIntel',
+    'IvyBridge-IBRS' => 'GenuineIntel',
     Haswell => 'GenuineIntel',
+    'Haswell-IBRS' => 'GenuineIntel',
     'Haswell-noTSX' => 'GenuineIntel',
+    'Haswell-noTSX-IBRS' => 'GenuineIntel',
     Broadwell => 'GenuineIntel',
+    'Broadwell-IBRS' => 'GenuineIntel',
     'Broadwell-noTSX' => 'GenuineIntel',
-    
+    'Broadwell-noTSX-IBRS' => 'GenuineIntel',
+    'Skylake-Client' => 'GenuineIntel',
+    'Skylake-Client-IBRS' => 'GenuineIntel',
+    'Skylake-Server' => 'GenuineIntel',
+    'Skylake-Server-IBRS' => 'GenuineIntel',
+
     # AMD CPUs
     athlon => 'AuthenticAMD',
     phenom  => 'AuthenticAMD',
@@ -133,6 +138,8 @@ my $cpu_vendor_list = {
     Opteron_G3  => 'AuthenticAMD',
     Opteron_G4  => 'AuthenticAMD',
     Opteron_G5  => 'AuthenticAMD',
+    EPYC => 'AuthenticAMD',
+    'EPYC-IBPB' => 'AuthenticAMD',
 
     # generic types, use vendor from host node
     host => 'default',
@@ -140,8 +147,11 @@ my $cpu_vendor_list = {
     kvm64 => 'default',
     qemu32 => 'default',
     qemu64 => 'default',
+    max => 'default',
 };
 
+my $cpu_flag = qr/[+-](pcid|spec-ctrl)/;
+
 my $cpu_fmt = {
     cputype => {
        description => "Emulated CPU type.",
@@ -156,6 +166,15 @@ my $cpu_fmt = {
        optional => 1,
        default => 0
     },
+    flags => {
+       description => "List of additional CPU flags separated by ';'."
+                    . " Use '+FLAG' to enable, '-FLAG' to disable a flag."
+                    . " Currently supported flags: 'pcid', 'spec-ctrl'.",
+       format_description => '+FLAG[;-FLAG...]',
+       type => 'string',
+       pattern => qr/$cpu_flag(;$cpu_flag)*/,
+       optional => 1,
+    },
 };
 
 my $watchdog_fmt = {
@@ -220,9 +239,9 @@ my $confdesc = {
        optional => 1,
        type => 'integer',
         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,
+       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.",
+       minimum => 2,
+       maximum => 262144,
        default => 1024,
     },
     memory => {
@@ -249,9 +268,10 @@ my $confdesc = {
     keyboard => {
        optional => 1,
        type => 'string',
-       description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.",
+       description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.".
+                      "It should not be necessary to set it.",
        enum => PVE::Tools::kvmkeymaplist(),
-       default => 'en-us',
+       default => undef,
     },
     name => {
        optional => 1,
@@ -273,7 +293,7 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26 solaris)],
+        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
@@ -287,7 +307,8 @@ w2k3;; Microsoft Windows 2003
 w2k8;; Microsoft Windows 2008
 wvista;; Microsoft Windows Vista
 win7;; Microsoft Windows 7
-win8;; Microsoft Windows 8/2012
+win8;; Microsoft Windows 8/2012/2012r2
+win10;; Microsoft Windows 10/2016
 l24;; Linux 2.4 Kernel
 l26;; Linux 2.6/3.X Kernel
 solaris;; Solaris/OpenSolaris/OpenIndiania kernel
@@ -483,6 +504,10 @@ EODESCR
        type => 'string', format => 'pve-volume-id',
        description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
     },
+    vmstatestorage => get_standard_option('pve-storage-id', {
+       description => "Default storage for VM state volumes/files.",
+       optional => 1,
+    }),
     machine => {
        description => "Specific the Qemu machine type.",
        type => 'string',
@@ -613,7 +638,6 @@ my $net_fmt = {
     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'.",
-       format_description => 'model',
         enum => $nic_model_list,
         default_key => 1,
     },
@@ -731,7 +755,9 @@ my %drivedesc_base = (
     },
     snapshot => {
        type => 'boolean',
-       description => "Whether the drive should be included when making snapshots.",
+       description => "Controls qemu's snapshot mode feature."
+           . " If activated, changes made to the disk are temporary and will"
+           . " be discarded when the VM is shutdown.",
        optional => 1,
     },
     cache => {
@@ -753,6 +779,18 @@ my %drivedesc_base = (
        description => "Whether the drive should be included when making backups.",
        optional => 1,
     },
+    replicate => {
+       type => 'boolean',
+       description => 'Whether the drive should considered for replication jobs.',
+       optional => 1,
+       default => 1,
+    },
+    rerror => {
+       type => 'string',
+       enum => [qw(ignore report stop)],
+       description => 'Read error action.',
+       optional => 1,
+    },
     werror => {
        type => 'string',
        enum => [qw(enospc ignore report stop)],
@@ -783,16 +821,14 @@ my %drivedesc_base = (
        maxLength => 20*3, # *3 since it's %xx url enoded
        description => "The drive's reported serial number, url-encoded, up to 20 bytes long.",
        optional => 1,
-    }
-);
-
-my %rerror_fmt = (
-    rerror => {
-       type => 'string',
-       enum => [qw(ignore report stop)],
-       description => 'Read error action.',
-       optional => 1,
     },
+    shared => {
+       type => 'boolean',
+       description => 'Mark this locally-managed volume as available on all nodes',
+       verbose_description => "Mark this locally-managed volume as available on all nodes.\n\nWARNING: This option does not share the volume automatically, it assumes it is shared already!",
+       optional => 1,
+       default => 0,
+    }
 );
 
 my %iothread_fmt = ( iothread => {
@@ -821,6 +857,15 @@ my %queues_fmt = (
     }
 );
 
+my %scsiblock_fmt = (
+    scsiblock => {
+       type => 'boolean',
+       description => "whether to use scsi-block for full passthrough of host block device\n\nWARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host",
+       optional => 1,
+       default => 0,
+    },
+);
+
 my $add_throttle_desc = sub {
     my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
     my $d = {
@@ -852,16 +897,21 @@ $add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool',  'io
 $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);
+$add_throttle_desc->('bps_max_length',     'integer', 'length of I/O bursts',       'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_rd_max_length',  'integer', 'length of read I/O bursts',  'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_wr_max_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_max_length', 'integer', 'length of read I/O bursts',  'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
+
+# legacy support
+$drivedesc_base{'bps_rd_length'} = { alias => 'bps_rd_max_length' };
+$drivedesc_base{'bps_wr_length'} = { alias => 'bps_wr_max_length' };
+$drivedesc_base{'iops_rd_length'} = { alias => 'iops_rd_max_length' };
+$drivedesc_base{'iops_wr_length'} = { alias => 'iops_wr_max_length' };
 
 my $ide_fmt = {
     %drivedesc_base,
-    %rerror_fmt,
     %model_fmt,
 };
 PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
@@ -877,6 +927,7 @@ my $scsi_fmt = {
     %drivedesc_base,
     %iothread_fmt,
     %queues_fmt,
+    %scsiblock_fmt,
 };
 my $scsidesc = {
     optional => 1,
@@ -887,7 +938,6 @@ PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
 
 my $sata_fmt = {
     %drivedesc_base,
-    %rerror_fmt,
 };
 my $satadesc = {
     optional => 1,
@@ -899,7 +949,6 @@ PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
 my $virtio_fmt = {
     %drivedesc_base,
     %iothread_fmt,
-    %rerror_fmt,
 };
 my $virtiodesc = {
     optional => 1,
@@ -910,10 +959,10 @@ PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
 
 my $alldrive_fmt = {
     %drivedesc_base,
-    %rerror_fmt,
     %iothread_fmt,
     %model_fmt,
     %queues_fmt,
+    %scsiblock_fmt,
 };
 
 my $efidisk_fmt = {
@@ -1000,6 +1049,13 @@ EODESCR
        optional => 1,
        default => 1,
     },
+    romfile => {
+        type => 'string',
+        pattern => '[^,;]+',
+        format_description => 'string',
+        description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
+        optional => 1,
+    },
     pcie => {
        type => 'boolean',
         description =>  "Choose the PCI-express bus (needs the 'q35' machine model).",
@@ -1186,6 +1242,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',
     };
@@ -1346,6 +1403,12 @@ sub parse_drive {
 
     # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
     for my $requirement (
+       [mbps_max => 'mbps'],
+       [mbps_rd_max => 'mbps_rd'],
+       [mbps_wr_max => 'mbps_wr'],
+       [miops_max => 'miops'],
+       [miops_rd_max => 'miops_rd'],
+       [miops_wr_max => 'miops_wr'],
        [bps_max_length => 'mbps_max'],
        [bps_rd_max_length => 'mbps_rd_max'],
        [bps_wr_max_length => 'mbps_wr_max'],
@@ -1486,7 +1549,7 @@ sub print_drivedevice_full {
            if ($drive->{file} =~ m|^/|) {
                $path = $drive->{file};
                if (my $info = path_is_scsi($path)) {
-                   if ($info->{type} == 0) {
+                   if ($info->{type} == 0 && $drive->{scsiblock}) {
                        $devicetype = 'block';
                    } elsif ($info->{type} == 1) { # tape
                        $devicetype = 'generic';
@@ -1570,10 +1633,39 @@ sub print_drive_full {
    }
 
     my $opts = '';
-    my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max);
+    my @qemu_drive_options = qw(heads secs cyls trans media format cache rerror werror aio discard);
     foreach my $o (@qemu_drive_options) {
-       $opts .= ",$o=$drive->{$o}" if $drive->{$o};
+       $opts .= ",$o=$drive->{$o}" if defined($drive->{$o});
     }
+
+    # snapshot only accepts on|off
+    if (defined($drive->{snapshot})) {
+       my $v = $drive->{snapshot} ? 'on' : 'off';
+       $opts .= ",snapshot=$v";
+    }
+
+    foreach my $type (['', '-total'], [_rd => '-read'], [_wr => '-write']) {
+       my ($dir, $qmpname) = @$type;
+       if (my $v = $drive->{"mbps$dir"}) {
+           $opts .= ",throttling.bps$qmpname=".int($v*1024*1024);
+       }
+       if (my $v = $drive->{"mbps${dir}_max"}) {
+           $opts .= ",throttling.bps$qmpname-max=".int($v*1024*1024);
+       }
+       if (my $v = $drive->{"bps${dir}_max_length"}) {
+           $opts .= ",throttling.bps$qmpname-max-length=$v";
+       }
+       if (my $v = $drive->{"iops${dir}"}) {
+           $opts .= ",throttling.iops$qmpname=$v";
+       }
+       if (my $v = $drive->{"iops${dir}_max"}) {
+           $opts .= ",throttling.iops$qmpname-max=$v";
+       }
+       if (my $v = $drive->{"iops${dir}_max_length"}) {
+           $opts .= ",throttling.iops$qmpname-max-length=$v";
+       }
+    }
+
     if (my $serial = $drive->{serial}) {
        $serial = URI::Escape::uri_unescape($serial);
        $opts .= ",serial=$serial";
@@ -1581,11 +1673,6 @@ sub print_drive_full {
 
     $opts .= ",format=$format" if $format && !$drive->{format};
 
-    foreach my $o (qw(bps bps_rd bps_wr)) {
-       my $v = $drive->{"m$o"};
-       $opts .= ",$o=" . int($v*1024*1024) if $v;
-    }
-
     my $cache_direct = 0;
 
     if (my $cache = $drive->{cache}) {
@@ -1699,20 +1786,18 @@ sub print_netdev_full {
 sub print_cpu_device {
     my ($conf, $id) = @_;
 
-    my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
-    my $cpu = $nokvm ? "qemu64" : "kvm64";
+    my $kvm = $conf->{kvm} // 1;
+    my $cpu = $kvm ? "kvm64" : "qemu64";
     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);
+    my $current_socket = int(($id - 1 - $current_core)/$cores);
 
     return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
 }
@@ -2069,6 +2154,23 @@ sub destroy_vm {
 
     PVE::QemuConfig->check_lock($conf) if !$skiplock;
 
+    if ($conf->{template}) {
+       # check if any base image is still used by a linked clone
+       foreach_drive($conf, sub {
+               my ($ds, $drive) = @_;
+
+               return if drive_is_cdrom($drive);
+
+               my $volid = $drive->{file};
+
+               return if !$volid || $volid =~ m|^/|;
+
+               die "base volume '$volid' is still in use by linked cloned\n"
+                   if PVE::Storage::volume_is_base_and_used($storecfg, $volid);
+
+       });
+    }
+
     # only remove disks owned by this VM
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
@@ -2082,7 +2184,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) {
@@ -2526,6 +2632,8 @@ sub vmstatus {
     my $storecfg = PVE::Storage::config();
 
     my $list = vzlist();
+    my $defaults = load_defaults();
+
     my ($uptime) = PVE::ProcFSTools::read_proc_uptime(1);
 
     my $cpucount = $cpuinfo->{cpus} || 1;
@@ -2551,16 +2659,19 @@ sub vmstatus {
            $d->{maxdisk} = 0;
        }
 
-       $d->{cpus} = ($conf->{sockets} || 1) * ($conf->{cores} || 1);
+       $d->{cpus} = ($conf->{sockets} || $defaults->{sockets})
+           * ($conf->{cores} || $defaults->{cores});
        $d->{cpus} = $cpucount if $d->{cpus} > $cpucount;
        $d->{cpus} = $conf->{vcpus} if $conf->{vcpus};
 
        $d->{name} = $conf->{name} || "VM $vmid";
-       $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0;
+       $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024)
+           : $defaults->{memory}*(1024*1024);
 
        if ($conf->{balloon}) {
            $d->{balloon_min} = $conf->{balloon}*(1024*1024);
-           $d->{shares} = defined($conf->{shares}) ? $conf->{shares} : 1000;
+           $d->{shares} = defined($conf->{shares}) ? $conf->{shares}
+               : $defaults->{shares};
        }
 
        $d->{uptime} = 0;
@@ -2575,6 +2686,8 @@ sub vmstatus {
 
         $d->{template} = PVE::QemuConfig->is_template($conf);
 
+       $d->{serial} = 1 if conf_has_serial($conf);
+
        $res->{$vmid} = $d;
     }
 
@@ -2704,7 +2817,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);
@@ -2733,24 +2846,37 @@ sub foreach_volid {
     my $volhash = {};
 
     my $test_volid = sub {
-       my ($volid, $is_cdrom) = @_;
+       my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
 
        return if !$volid;
 
-       $volhash->{$volid} = $is_cdrom || 0;
+       $volhash->{$volid}->{cdrom} //= 1;
+       $volhash->{$volid}->{cdrom} = 0 if !$is_cdrom;
+
+       $volhash->{$volid}->{replicate} //= 0;
+       $volhash->{$volid}->{replicate} = 1 if $replicate;
+
+       $volhash->{$volid}->{shared} //= 0;
+       $volhash->{$volid}->{shared} = 1 if $shared;
+
+       $volhash->{$volid}->{referenced_in_config} //= 0;
+       $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname);
+
+       $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
+           if defined($snapname);
     };
 
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
-       &$test_volid($drive->{file}, drive_is_cdrom($drive));
+       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef);
     });
 
     foreach my $snapname (keys %{$conf->{snapshots}}) {
        my $snap = $conf->{snapshots}->{$snapname};
-       &$test_volid($snap->{vmstate}, 0);
+       $test_volid->($snap->{vmstate}, 0, 1, $snapname);
        foreach_drive($snap, sub {
            my ($ds, $drive) = @_;
-           &$test_volid($drive->{file}, drive_is_cdrom($drive));
+           $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, $snapname);
         });
     }
 
@@ -2759,6 +2885,18 @@ sub foreach_volid {
     }
 }
 
+sub conf_has_serial {
+    my ($conf) = @_;
+
+    for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++)  {
+       if ($conf->{"serial$i"}) {
+           return 1;
+       }
+    }
+
+    return 0;
+}
+
 sub vga_conf_has_spice {
     my ($vga) = @_;
 
@@ -2781,6 +2919,11 @@ sub config_to_command {
     my $kvmver = kvm_user_version();
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
+    my $winversion = windows_version($ostype);
+    my $kvm = $conf->{kvm} // 1;
+
+    die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n" if (!$cpuinfo->{hvm} && $kvm);
+
     if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
        $vernum = $1*1000000+$2*1000;
     } elsif ($kvmver =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
@@ -2820,44 +2963,34 @@ sub config_to_command {
     }
 
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-       my $ovmfbase;
+       die "uefi base image not found\n" if ! -f $OVMF_CODE;
 
-       # prefer the OVMF_CODE variant
-       if (-f $OVMF_CODE) {
-           $ovmfbase = $OVMF_CODE;
-       } elsif (-f $OVMF_IMG) {
-           $ovmfbase = $OVMF_IMG;
-       }
-
-       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 $path;
+       my $format;
+       if (my $efidisk = $conf->{efidisk0}) {
+           my $d = PVE::JSONSchema::parse_property_string($efidisk_fmt, $efidisk);
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
+           $format = $d->{format};
            if ($storeid) {
                $path = PVE::Storage::path($storecfg, $d->{file});
-               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-               $format = qemu_img_format($scfg, $volname);
+               if (!defined($format)) {
+                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+                   $format = qemu_img_format($scfg, $volname);
+               }
            } else {
                $path = $d->{file};
-               $format = "raw";
+               die "efidisk format must be specified\n"
+                   if !defined($format);
            }
-           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
+           warn "no efidisk configured! Using temporary efivars disk.\n";
+           $path = "/tmp/$vmid-ovmf.fd";
+           PVE::Tools::file_copy($OVMF_VARS, $path, -s $OVMF_VARS);
+           $format = 'raw';
        }
+
+       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$OVMF_CODE";
+       push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0,file=$path";
     }
 
 
@@ -2870,12 +3003,10 @@ sub config_to_command {
     $vga = 'qxl' if $qxlnum;
 
     if (!$vga) {
-       if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
-                               $conf->{ostype} eq 'win7' ||
-                               $conf->{ostype} eq 'w2k8')) {
-           $vga = 'std';
+       if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
+           $vga = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
        } else {
-           $vga = 'cirrus';
+           $vga = ($winversion >= 6) ? 'std' : 'cirrus';
        }
     }
 
@@ -2892,6 +3023,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"});
@@ -2906,14 +3039,15 @@ sub config_to_command {
        }
 
        my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
+       my $romfile = $d->{romfile};
+
        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 = "";
            }
@@ -2933,6 +3067,7 @@ sub config_to_command {
            if($j == 0){
                $devicestr .= "$rombar$xvga";
                $devicestr .= ",multifunction=on" if $multifunction;
+               $devicestr .= ",romfile=/usr/share/kvm/$romfile" if $romfile;
            }
 
            push @$devices, '-device', $devicestr;
@@ -3028,52 +3163,26 @@ sub config_to_command {
     # time drift fix
     my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
 
-    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});
+    if ($winversion >= 5) { # 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 (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
-                   push @$cpuFlags , 'hv_reset' if !$nokvm;
-                   push @$cpuFlags , 'hv_vpindex' if !$nokvm;
-                   push @$cpuFlags , 'hv_runtime' if !$nokvm;
-               }
-
-           } 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;
 
-    if ($nokvm) {
+    if (!$kvm) {
        push @$machineFlags, 'accel=tcg';
-    } else {
-       die "No accelerator found!\n" if !$cpuinfo->{hvm};
     }
 
     if ($machine_type) {
@@ -3086,12 +3195,16 @@ sub config_to_command {
        push @$rtcFlags, 'base=localtime';
     }
 
-    my $cpu = $nokvm ? "qemu64" : "kvm64";
+    my $cpu = $kvm ? "kvm64" : "qemu64";
     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};
        $kvm_off = 1 if $cpuconf->{hidden};
+
+       if (defined(my $flags = $cpuconf->{flags})) {
+           push @$cpuFlags, split(";", $flags);
+       }
     }
 
     push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64';
@@ -3105,11 +3218,13 @@ 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 , '+kvm_pv_unhalt' if $kvm;
+       push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
     }
 
-    push @$cpuFlags, 'enforce' if $cpu ne 'host' && !$nokvm;
+    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) if $kvm;
+
+    push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm;
 
     push @$cpuFlags, 'kvm=off' if $kvm_off;
 
@@ -3148,7 +3263,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";
@@ -3164,9 +3279,12 @@ sub config_to_command {
 
        my $nodename = PVE::INotify::nodename();
        my $pfamily = PVE::Tools::get_host_address_family($nodename);
-       $spice_port = PVE::Tools::next_spice_port($pfamily);
+       my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);
+       die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs;
+       my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
+       $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
 
-       push @$devices, '-spice', "tls-port=${spice_port},addr=localhost,tls-ciphers=DES-CBC3-SHA,seamless-migration=on";
+       push @$devices, '-spice', "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
 
        push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
        push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
@@ -3204,6 +3322,9 @@ sub config_to_command {
            push @$vollist, $drive->{file};
        }
 
+       # ignore efidisk here, already added in bios/fw handling code above
+       return if $drive->{interface} eq 'efidisk';
+
        $use_virtio = 1 if $ds =~ m/^virtio/;
 
        if (drive_is_cdrom ($drive)) {
@@ -3253,11 +3374,6 @@ 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);
@@ -3528,10 +3644,6 @@ sub vm_deviceunplug {
 
     } elsif ($deviceid =~ m/^(scsi)(\d+)$/) {
 
-       #qemu 2.3 segfault on drive_del with virtioscsi + iothread
-       my $device = parse_drive($deviceid, $conf->{$deviceid});
-       die "virtioscsi with iothread is not hot-unplugglable currently" if $device->{iothread};
-
         qemu_devicedel($vmid, $deviceid);
         qemu_drivedel($vmid, $deviceid);
        qemu_deletescsihw($conf, $vmid, $deviceid);
@@ -3990,7 +4102,7 @@ sub qemu_block_resize {
 
     my $running = check_running($vmid);
 
-    return if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
+    $size = 0 if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
 
     return if !$running;
 
@@ -4015,11 +4127,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 {
@@ -4055,6 +4167,7 @@ my $fast_plug_option = {
     'startup' => 1,
     'description' => 1,
     'protection' => 1,
+    'vmstatestorage' => 1,
 };
 
 # hotplug changes in [PENDING]
@@ -4469,7 +4582,7 @@ sub vmconfig_update_disk {
 
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
-       $forcemachine, $spice_ticket, $migration_network, $migration_type) = @_;
+       $forcemachine, $spice_ticket, $migration_network, $migration_type, $targetstorage) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
        my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
@@ -4490,6 +4603,54 @@ 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;
@@ -4500,6 +4661,14 @@ sub vm_start {
                my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
                my $nodename = PVE::INotify::nodename();
 
+               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) {
@@ -4563,7 +4732,8 @@ sub vm_start {
        my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
                                                  : $defaults->{cpuunits};
 
-       my %run_params = (timeout => $statefile ? undef : 30, umask => 0077);
+       my $start_timeout = $conf->{hugepages} ? 300 : 30;
+       my %run_params = (timeout => $statefile ? undef : $start_timeout, umask => 0077);
 
        my %properties = (
            Slice => 'qemu.slice',
@@ -4614,13 +4784,32 @@ sub vm_start {
 
        print "migration listens on $migrate_uri\n" if $migrate_uri;
 
-       if ($statefile && $statefile ne 'tcp')  {
+       if ($statefile && $statefile ne 'tcp' && $statefile ne 'unix')  {
            eval { vm_mon_cmd_nocheck($vmid, "cont"); };
            warn $@ if $@;
        }
 
-       if ($migratedfrom) {
+       #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 {
                set_migration_caps($vmid);
            };
@@ -4635,7 +4824,6 @@ sub vm_start {
            }
 
        } else {
-
            if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
                vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
                    if $conf->{balloon};
@@ -4747,7 +4935,7 @@ sub get_vm_volumes {
 
     my $vollist = [];
     foreach_volid($conf, sub {
-       my ($volid, $is_cdrom) = @_;
+       my ($volid, $attr) = @_;
 
        return if $volid =~ m|^/|;
 
@@ -5176,6 +5364,13 @@ sub restore_update_config_line {
        } else {
            print $outfd $line;
        }
+    } elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) {
+       my ($uuid, $uuid_str);
+       UUID::generate($uuid);
+       UUID::unparse($uuid, $uuid_str);
+       my $smbios1 = parse_smbios1($2);
+       $smbios1->{uuid} = $uuid_str;
+       print $outfd $1.print_smbios1($smbios1)."\n";
     } else {
        print $outfd $line;
     }
@@ -5244,13 +5439,14 @@ sub update_disksize {
 
     my $changes;
 
-    my $used = {};
+    # used and unused disks
+    my $referenced = {};
 
     # Note: it is allowed to define multiple storages with same path (alias), so
     # we need to check both 'volid' and real 'path' (two different volid can point
     # to the same path).
 
-    my $usedpath = {};
+    my $referencedpath = {};
 
     # update size info
     foreach my $opt (keys %$conf) {
@@ -5259,10 +5455,10 @@ sub update_disksize {
            my $volid = $drive->{file};
            next if !$volid;
 
-           $used->{$volid} = 1;
+           $referenced->{$volid} = 1;
            if ($volid_hash->{$volid} &&
                (my $path = $volid_hash->{$volid}->{path})) {
-               $usedpath->{$path} = 1;
+               $referencedpath->{$path} = 1;
            }
 
            next if drive_is_cdrom($drive);
@@ -5282,21 +5478,24 @@ sub update_disksize {
        next if $opt !~ m/^unused\d+$/;
        my $volid = $conf->{$opt};
        my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};
-       if ($used->{$volid} || ($path && $usedpath->{$path})) {
+       if ($referenced->{$volid} || ($path && $referencedpath->{$path})) {
            $changes = 1;
            delete $conf->{$opt};
        }
+
+       $referenced->{$volid} = 1;
+       $referencedpath->{$path} = 1 if $path;
     }
 
     foreach my $volid (sort keys %$volid_hash) {
        next if $volid =~ m/vm-$vmid-state-/;
-       next if $used->{$volid};
+       next if $referenced->{$volid};
        my $path = $volid_hash->{$volid}->{path};
        next if !$path; # just to be sure
-       next if $usedpath->{$path};
+       next if $referencedpath->{$path};
        $changes = 1;
        PVE::QemuConfig->add_unused_volume($conf, $volid);
-       $usedpath->{$path} = 1; # avoid to add more than once (aliases)
+       $referencedpath->{$path} = 1; # avoid to add more than once (aliases)
     }
 
     return $changes;
@@ -5369,9 +5568,10 @@ sub restore_vma_archive {
     rmtree $tmpdir;
 
     # disable interrupts (always do cleanups)
-    local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
-       warn "got interrupt - ignored\n";
-    };
+    local $SIG{INT} =
+       local $SIG{TERM} =
+       local $SIG{QUIT} =
+       local $SIG{HUP} = sub { warn "got interrupt - ignored\n"; };
 
     my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
     POSIX::mkfifo($mapfifo, 0600);
@@ -5530,9 +5730,11 @@ sub restore_vma_archive {
 
     eval {
        # enable interrupts
-       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
-           die "interrupted by signal\n";
-       };
+       local $SIG{INT} =
+           local $SIG{TERM} =
+           local $SIG{QUIT} =
+           local $SIG{HUP} =
+           local $SIG{PIPE} = sub { die "interrupted by signal\n"; };
        local $SIG{ALRM} = sub { die "got timeout\n"; };
 
        $oldtimeout = alarm($timeout);
@@ -5646,15 +5848,18 @@ sub restore_tar_archive {
     my $tmpfn = "$conffile.$$.tmp";
 
     # disable interrupts (always do cleanups)
-    local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
-       print STDERR "got interrupt - ignored\n";
-    };
+    local $SIG{INT} =
+       local $SIG{TERM} =
+       local $SIG{QUIT} =
+       local $SIG{HUP} = sub { print STDERR "got interrupt - ignored\n"; };
 
     eval {
        # enable interrupts
-       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
-           die "interrupted by signal\n";
-       };
+       local $SIG{INT} =
+           local $SIG{TERM} =
+           local $SIG{QUIT} =
+           local $SIG{HUP} =
+           local $SIG{PIPE} = sub { die "interrupted by signal\n"; };
 
        if ($archive eq '-') {
            print "extracting archive from STDIN\n";
@@ -5718,23 +5923,20 @@ sub restore_tar_archive {
     warn $@ if $@;
 };
 
-sub foreach_writable_storage {
+sub foreach_storage_used_by_vm {
     my ($conf, $func) = @_;
 
     my $sidhash = {};
 
-    foreach my $ds (keys %$conf) {
-       next if !is_valid_drivename($ds);
-
-       my $drive = parse_drive($ds, $conf->{$ds});
-       next if !$drive;
-       next if drive_is_cdrom($drive);
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+       return if drive_is_cdrom($drive);
 
        my $volid = $drive->{file};
 
        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
        $sidhash->{$sid} = $sid if $sid;
-    }
+    });
 
     foreach my $sid (sort keys %$sidhash) {
        &$func($sid);
@@ -5763,7 +5965,7 @@ sub qga_check_running {
 
     eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); };
     if ($@) {
-       warn "Qemu Guest Agent are not running - $@";
+       warn "Qemu Guest Agent is not running - $@";
        return 0;
     }
     return 1;
@@ -5849,91 +6051,178 @@ 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, $qga) = @_;
 
-    my $storecfg = PVE::Storage::config();
-    my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
+    $jobs = {} if !$jobs;
 
-    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+    my $qemu_target;
+    my $format;
+    $jobs->{"drive-$drive"} = {};
 
-    my $format = qemu_img_format($dst_scfg, $dst_volname);
+    if ($dst_volid =~ /^nbd:/) {
+       $qemu_target = $dst_volid;
+       $format = "nbd";
+    } else {
+       my $storecfg = PVE::Storage::config();
+       my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
 
-    my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
 
-    my $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+       $format = qemu_img_format($dst_scfg, $dst_volname);
+
+       my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+
+       $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";
 
-    my $finish_job = sub {
-       while (1) {
-           my $stats = vm_mon_cmd($vmid, "query-block-jobs");
-           my $stat = @$stats[0];
-           last if !$stat;
-           sleep 1;
-       }
-    };
+    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";
+    }
+
+    qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
+}
+
+sub qemu_drive_mirror_monitor {
+    my ($vmid, $vmiddst, $jobs, $skipcomplete, $qga) = @_;
 
     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;
+
+           foreach my $job (keys %$jobs) {
+
+               if(defined($jobs->{$job}->{complete}) && !defined($running_mirror_jobs->{$job})) {
+                   print "$job : finished\n";
+                   delete $jobs->{$job};
+                   next;
+               }
+
+               die "$job: mirroring has been cancelled\n" if !defined($running_mirror_jobs->{$job});
 
-           if (my $total = $stat->{len}) {
-               my $transferred = $stat->{offset} || 0;
-               my $remaining = $total - $transferred;
-               my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+               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);
 
-               print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+                   print "$job: transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+               }
+
+               $readycounter++ if $running_mirror_jobs->{$job}->{ready};
            }
 
+           last if scalar(keys %$jobs) == 0;
+
+           if ($readycounter == scalar(keys %$jobs)) {
+               print "all mirroring jobs are ready \n";
+               last if $skipcomplete; #do the complete later
 
-           if ($stat->{ready} eq 'true') {
+               if ($vmiddst && $vmiddst != $vmid) {
+                   my $agent_running = $qga && qga_check_running($vmid);
+                   if ($agent_running) {
+                       print "freeze filesystem\n";
+                       eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+                   } else {
+                       print "suspend vm\n";
+                       eval { PVE::QemuServer::vm_suspend($vmid, 1); };
+                   }
 
-               last if $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);
+
+                   if ($agent_running) {
+                       print "unfreeze filesystem\n";
+                       eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
+                   } else {
+                       print "resume vm\n";
+                       eval {  PVE::QemuServer::vm_resume($vmid, 1, 1); };
+                   }
 
-               # 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") };
-               if (!$@) {
-                   &$finish_job();
                    last;
+               } else {
+
+                   foreach my $job (keys %$jobs) {
+                       # try to switch the disk if source and destination are on the same guest
+                       print "$job: Completing block job...\n";
+
+                       eval { vm_mon_cmd($vmid, "block-job-complete", device => $job) };
+                       if ($@ =~ m/cannot be completed/) {
+                           print "$job: Block job cannot be completed, try again.\n";
+                           $err_complete++;
+                       }else {
+                           print "$job: Completed successfully.\n";
+                           $jobs->{$job}->{complete} = 1;
+                       }
+                   }
                }
-               die $@ if $@ !~ m/cannot be completed/;
            }
            sleep 1;
        }
-
-
     };
     my $err = $@;
 
-    my $cancel_job = sub {
-       vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
-       &$finish_job();
-    };
-
     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: Cancelling 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: Done.\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, $qga) = @_;
 
     my $newvolid;
 
@@ -5942,23 +6231,15 @@ 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;
 
-       my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
-       if (!$format) {
-           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-           $format = qemu_img_format($scfg, $volname);
-       }
-
-       # test if requested format is supported - else use default
-       my $supported = grep { $_ eq $format } @$validFormats;
-       $format = $defFormat if !$supported;
-
+       my $dst_format = resolve_dst_disk_format($storecfg, $storeid, $volname, $format);
        my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
 
        print "create full clone of drive $drivename ($drive->{file})\n";
-       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $format, undef, ($size/1024));
+       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, undef, ($size/1024));
        push @$newvollist, $newvolid;
 
        PVE::Storage::activate_volumes($storecfg, [$newvolid]);
@@ -5974,7 +6255,7 @@ sub clone_disk {
                    if $drive->{iothread};
            }
 
-           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit);
+           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga);
        }
     }
 
@@ -6073,6 +6354,24 @@ sub qemu_use_old_bios_files {
     return ($use_old_bios_files, $machine_type);
 }
 
+sub create_efidisk {
+    my ($storecfg, $storeid, $vmid, $fmt) = @_;
+
+    die "EFI vars default image not found\n" if ! -f $OVMF_VARS;
+
+    my $vars_size = PVE::Tools::convert_size(-s $OVMF_VARS, 'b' => 'kb');
+    my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
+    PVE::Storage::activate_volumes($storecfg, [$volid]);
+
+    my $path = PVE::Storage::path($storecfg, $volid);
+    eval {
+       run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $OVMF_VARS, $path]);
+    };
+    die "Copying EFI vars image failed: $@" if $@;
+
+    return ($volid, $vars_size);
+}
+
 sub lspci {
 
     my $devices = {};
@@ -6083,6 +6382,12 @@ sub lspci {
            push @{$devices->{$id}}, $res;
     });
 
+    # Entries should be sorted by functions.
+    foreach my $id (keys %$devices) {
+       my $dev = $devices->{$id};
+       $devices->{$id} = [ sort { $a->{function} <=> $b->{function} } @$dev ];
+    }
+
     return $devices;
 }
 
@@ -6104,7 +6409,7 @@ sub scsihw_infos {
 
     my $maxdev = 0;
 
-    if ($conf->{scsihw} && ($conf->{scsihw} =~ m/^lsi/)) {
+    if (!$conf->{scsihw} || ($conf->{scsihw} =~ m/^lsi/)) {
         $maxdev = 7;
     } elsif ($conf->{scsihw} && ($conf->{scsihw} eq 'virtio-scsi-single')) {
         $maxdev = 1;
@@ -6118,6 +6423,91 @@ sub scsihw_infos {
     return ($maxdev, $controller, $controller_prefix);
 }
 
+sub add_hyperv_enlightenments {
+    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough) = @_;
+
+    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;
+}
+
+sub resolve_dst_disk_format {
+       my ($storecfg, $storeid, $src_volname, $format) = @_;
+       my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+
+       if (!$format) {
+           # if no target format is specified, use the source disk format as hint
+           if ($src_volname) {
+               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+               $format = qemu_img_format($scfg, $src_volname);
+           } else {
+               return $defFormat;
+           }
+       }
+
+       # test if requested format is supported - else use default
+       my $supported = grep { $_ eq $format } @$validFormats;
+       $format = $defFormat if !$supported;
+       return $format;
+}
+
+sub resolve_first_disk {
+    my $conf = shift;
+    my @disks = PVE::QemuServer::valid_drive_names();
+    my $firstdisk;
+    foreach my $ds (reverse @disks) {
+       next if !$conf->{$ds};
+       my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
+       next if PVE::QemuServer::drive_is_cdrom($disk);
+       $firstdisk = $ds;
+    }
+    return $firstdisk;
+}
+
+sub generate_smbios1_uuid {
+    my ($uuid, $uuid_str);
+    UUID::generate($uuid);
+    UUID::unparse($uuid, $uuid_str);
+    return "uuid=$uuid_str";
+}
+
 # bash completion helper
 
 sub complete_backup_archives {
@@ -6191,4 +6581,10 @@ sub complete_storage {
     return $res;
 }
 
+sub nbd_stop {
+    my ($vmid) = @_;
+
+    vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
 1;