]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
improve windows VM version pinning on VM creation
[qemu-server.git] / PVE / QemuServer.pm
index cfac03a92858236763b8f9637efd04af2f94f8b8..57cfe627eb24e74db75d5c2b0de3b0e2722cdd50 100644 (file)
@@ -27,12 +27,14 @@ use URI::Escape;
 use UUID;
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
+use PVE::CGroup;
 use PVE::DataCenterConfig;
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option parse_property_string);
 use PVE::ProcFSTools;
+use PVE::PBSClient;
 use PVE::RPCEnvironment;
 use PVE::Storage;
 use PVE::SysFSTools;
@@ -43,6 +45,7 @@ use PVE::QMPClient;
 use PVE::QemuConfig;
 use PVE::QemuServer::Helpers qw(min_version config_aware_timeout);
 use PVE::QemuServer::Cloudinit;
+use PVE::QemuServer::CGroup;
 use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options);
 use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom parse_drive print_drive);
 use PVE::QemuServer::Machine;
@@ -121,14 +124,6 @@ PVE::JSONSchema::register_standard_option('pve-targetstorage', {
 
 #no warnings 'redefine';
 
-sub cgroups_write {
-   my ($controller, $vmid, $option, $value) = @_;
-
-   my $path = "/sys/fs/cgroup/$controller/qemu.slice/$vmid.scope/$option";
-   PVE::ProcFSTools::write_proc_entry($path, $value);
-
-}
-
 my $nodename_cache;
 sub nodename {
     $nodename_cache //= PVE::INotify::nodename();
@@ -161,7 +156,7 @@ my $agent_fmt = {
        default_key => 1,
     },
     fstrim_cloned_disks => {
-       description => "Run fstrim after cloning/moving a disk.",
+       description => "Run fstrim after moving a disk or migrating the VM.",
        type => 'boolean',
        optional => 1,
        default => 0
@@ -216,7 +211,7 @@ my $audio_fmt = {
     },
     driver =>  {
        type => 'string',
-       enum => ['spice'],
+       enum => ['spice', 'none'],
        default => 'spice',
        optional => 1,
        description => "Driver backend for the audio device."
@@ -389,7 +384,7 @@ w2k8;; Microsoft Windows 2008
 wvista;; Microsoft Windows Vista
 win7;; Microsoft Windows 7
 win8;; Microsoft Windows 8/2012/2012r2
-win10;; Microsoft Windows 10/2016
+win10;; Microsoft Windows 10/2016/2019
 l24;; Linux 2.4 Kernel
 l26;; Linux 2.6 - 5.X Kernel
 solaris;; Solaris/OpenSolaris/OpenIndiania kernel
@@ -397,15 +392,14 @@ EODESC
     },
     boot => {
        optional => 1,
-       type => 'string',
-       description => "Boot on floppy (a), hard disk (c), CD-ROM (d), or network (n).",
-       pattern => '[acdn]{1,4}',
-       default => 'cdn',
+       type => 'string', format => 'pve-qm-boot',
+       description => "Specify guest boot order. Use with 'order=', usage with"
+                    . " no key or 'legacy=' is deprecated.",
     },
     bootdisk => {
        optional => 1,
        type => 'string', format => 'pve-qm-bootdisk',
-       description => "Enable booting from specified disk.",
+       description => "Enable booting from specified disk. Deprecated: Use 'boot: order=foo;bar' instead.",
        pattern => '(ide|sata|scsi|virtio)\d+',
     },
     smp => {
@@ -724,7 +718,7 @@ my $confdesc_cloudinit = {
        description => 'Specifies the cloud-init configuration format. The default depends on the'
            .' configured operating system type (`ostype`. We use the `nocloud` format for Linux,'
            .' and `configdrive2` for windows.',
-       enum => ['configdrive2', 'nocloud'],
+       enum => ['configdrive2', 'nocloud', 'opennebula'],
     },
     ciuser => {
        optional => 1,
@@ -998,7 +992,7 @@ sub verify_volume_id_or_qm_path {
     # 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;
+       return if $noerr;
        die $@;
     }
     return $volid;
@@ -1143,12 +1137,13 @@ sub verify_bootdev {
     return $dev if $check->("usb");
     return $dev if $check->("hostpci");
 
-    return undef if $noerr;
+    return if $noerr;
     die "invalid boot device '$dev'\n";
 }
 
 sub print_bootorder {
     my ($devs) = @_;
+    return "" if !@$devs;
     my $data = { order => join(';', @$devs) };
     return PVE::JSONSchema::print_property_string($data, $boot_fmt);
 }
@@ -1158,11 +1153,11 @@ my $kvm_api_version = 0;
 sub kvm_version {
     return $kvm_api_version if $kvm_api_version;
 
-    open my $fh, '<', '/dev/kvm'
-       or return undef;
+    open my $fh, '<', '/dev/kvm' or return;
 
     # 0xae00 => KVM_GET_API_VERSION
     $kvm_api_version = ioctl($fh, 0xae00, 0);
+    close($fh);
 
     return $kvm_api_version;
 }
@@ -1242,7 +1237,7 @@ sub filename_to_volume_id {
      if (!($file eq 'none' || $file eq 'cdrom' ||
          $file =~ m|^/dev/.+| || $file =~ m/^([^:]+):(.+)$/)) {
 
-       return undef if $file =~ m|/|;
+       return if $file =~ m|/|;
 
        if ($media && $media eq 'cdrom') {
            $file = "local:iso/$file";
@@ -1318,7 +1313,7 @@ sub pve_verify_hotplug_features {
 
     return $value if parse_hotplug_features($value);
 
-    return undef if $noerr;
+    return if $noerr;
 
     die "unable to parse hotplug option\n";
 }
@@ -1333,12 +1328,12 @@ sub scsi_inquiry {
     my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);
     if (!$ret) {
        die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
-       return undef;
+       return;
     }
     my $version = unpack("I", $versionbuf);
     if ($version < 30000) {
        die "scsi generic interface too old\n"  if !$noerr;
-       return undef;
+       return;
     }
 
     my $buf = "\x00" x 36;
@@ -1355,13 +1350,13 @@ sub scsi_inquiry {
     $ret = ioctl($fh, $SG_IO, $packet);
     if (!$ret) {
        die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
-       return undef;
+       return;
     }
 
     my @res = unpack($sg_io_hdr_t, $packet);
     if ($res[17] || $res[18]) {
        die "scsi ioctl SG_IO status error - $!\n" if !$noerr;
-       return undef;
+       return;
     }
 
     my $res = {};
@@ -1377,7 +1372,7 @@ sub scsi_inquiry {
 sub path_is_scsi {
     my ($path) = @_;
 
-    my $fh = IO::File->new("+<$path") || return undef;
+    my $fh = IO::File->new("+<$path") || return;
     my $res = scsi_inquiry($fh, 1);
     close($fh);
 
@@ -1403,7 +1398,7 @@ sub print_tabletdevice_full {
 sub print_keyboarddevice_full {
     my ($conf, $arch, $machine) = @_;
 
-    return undef if $arch ne 'aarch64';
+    return if $arch ne 'aarch64';
 
     return "usb-kbd,id=keyboard,bus=ehci.0,port=2";
 }
@@ -1507,7 +1502,7 @@ sub print_drivedevice_full {
 sub get_initiator_name {
     my $initiator;
 
-    my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return undef;
+    my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return;
     while (defined(my $line = <$fh>)) {
        next if $line !~ m/^\s*InitiatorName\s*=\s*([\.\-:\w]+)/;
        $initiator = $1;
@@ -1614,8 +1609,6 @@ sub print_drive_commandline_full {
 sub print_netdevice_full {
     my ($vmid, $conf, $net, $netid, $bridges, $use_old_bios_files, $arch, $machine_type) = @_;
 
-    my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
-
     my $device = $net->{model};
     if ($net->{model} eq 'virtio') {
          $device = 'virtio-net-pci';
@@ -1801,7 +1794,7 @@ sub parse_net {
     my $res = eval { parse_property_string($net_fmt, $data) };
     if ($@) {
        warn $@;
-       return undef;
+       return;
     }
     if (!defined($res->{macaddr})) {
        my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
@@ -1817,25 +1810,25 @@ sub parse_ipconfig {
     my $res = eval { parse_property_string($ipconfig_fmt, $data) };
     if ($@) {
        warn $@;
-       return undef;
+       return;
     }
 
     if ($res->{gw} && !$res->{ip}) {
        warn 'gateway specified without specifying an IP address';
-       return undef;
+       return;
     }
     if ($res->{gw6} && !$res->{ip6}) {
        warn 'IPv6 gateway specified without specifying an IPv6 address';
-       return undef;
+       return;
     }
     if ($res->{gw} && $res->{ip} eq 'dhcp') {
        warn 'gateway specified together with DHCP';
-       return undef;
+       return;
     }
     if ($res->{gw6} && $res->{ip6} !~ /^$IPV6RE/) {
        # gw6 + auto/dhcp
        warn "IPv6 gateway specified together with $res->{ip6} address";
-       return undef;
+       return;
     }
 
     if (!$res->{ip} && !$res->{ip6}) {
@@ -1873,7 +1866,7 @@ sub vm_is_volid_owner {
        }
     }
 
-    return undef;
+    return;
 }
 
 sub vmconfig_register_unused_drive {
@@ -1966,7 +1959,7 @@ PVE::JSONSchema::register_format('pve-qm-smbios1', $smbios1_fmt);
 sub parse_watchdog {
     my ($value) = @_;
 
-    return undef if !$value;
+    return if !$value;
 
     my $res = eval { parse_property_string($watchdog_fmt, $value) };
     warn $@ if $@;
@@ -1974,11 +1967,11 @@ sub parse_watchdog {
 }
 
 sub parse_guest_agent {
-    my ($value) = @_;
+    my ($conf) = @_;
 
-    return {} if !defined($value->{agent});
+    return {} if !defined($conf->{agent});
 
-    my $res = eval { parse_property_string($agent_fmt, $value->{agent}) };
+    my $res = eval { parse_property_string($agent_fmt, $conf->{agent}) };
     warn $@ if $@;
 
     # if the agent is disabled ignore the other potentially set properties
@@ -1986,6 +1979,14 @@ sub parse_guest_agent {
     return $res;
 }
 
+sub get_qga_key {
+    my ($conf, $key) = @_;
+    return undef if !defined($conf->{agent});
+
+    my $agent = parse_guest_agent($conf);
+    return $agent->{$key};
+}
+
 sub parse_vga {
     my ($value) = @_;
 
@@ -1998,7 +1999,7 @@ sub parse_vga {
 sub parse_rng {
     my ($value) = @_;
 
-    return undef if !$value;
+    return if !$value;
 
     my $res = eval { parse_property_string($rng_fmt, $value) };
     warn $@ if $@;
@@ -2011,7 +2012,7 @@ sub verify_usb_device {
 
     return $value if parse_usb_device($value);
 
-    return undef if $noerr;
+    return if $noerr;
 
     die "unable to parse usb device\n";
 }
@@ -2073,7 +2074,7 @@ sub check_type {
 }
 
 sub destroy_vm {
-    my ($storecfg, $vmid, $skiplock, $replacement_conf) = @_;
+    my ($storecfg, $vmid, $skiplock, $replacement_conf, $purge_unreferenced) = @_;
 
     my $conf = PVE::QemuConfig->load_config($vmid);
 
@@ -2094,8 +2095,8 @@ sub destroy_vm {
        });
     }
 
-    # only remove disks owned by this VM
-    PVE::QemuConfig->foreach_volume($conf, sub {
+    # only remove disks owned by this VM (referenced in the config)
+    PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, sub {
        my ($ds, $drive) = @_;
        return if drive_is_cdrom($drive, 1);
 
@@ -2109,13 +2110,14 @@ sub destroy_vm {
        warn "Could not remove disk '$volid', check manually: $@" if $@;
     });
 
-    # also remove unused disk
-    my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
-    PVE::Storage::foreach_volid($vmdisks, sub {
-       my ($volid, $sid, $volname, $d) = @_;
-       eval { PVE::Storage::vdisk_free($storecfg, $volid) };
-       warn $@ if $@;
-    });
+    if ($purge_unreferenced) { # also remove unreferenced disk
+       my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
+       PVE::Storage::foreach_volid($vmdisks, sub {
+           my ($volid, $sid, $volname, $d) = @_;
+           eval { PVE::Storage::vdisk_free($storecfg, $volid) };
+           warn $@ if $@;
+       });
+    }
 
     if (defined $replacement_conf) {
        PVE::QemuConfig->write_config($vmid, $replacement_conf);
@@ -2127,7 +2129,7 @@ sub destroy_vm {
 sub parse_vm_config {
     my ($filename, $raw) = @_;
 
-    return undef if !defined($raw);
+    return if !defined($raw);
 
     my $res = {
        digest => Digest::SHA::sha1_hex($raw),
@@ -2213,6 +2215,8 @@ sub parse_vm_config {
 
                $conf->{$key} = $value;
            }
+       } else {
+           warn "vm $vmid - unable to parse config: $line\n";
        }
     }
 
@@ -2553,6 +2557,16 @@ our $vmstatus_return_properties = {
        type => 'string',
        optional => 1,
     },
+    'running-machine' => {
+       description => "The currently running machine type (if running).",
+       type => 'string',
+       optional => 1,
+    },
+    'running-qemu' => {
+       description => "The currently running QEMU version (if running).",
+       type => 'string',
+       optional => 1,
+    },
 };
 
 my $last_proc_pid_stat;
@@ -2731,10 +2745,32 @@ sub vmstatus {
        $res->{$vmid}->{diskwrite} = $totalwrbytes;
     };
 
+    my $machinecb = sub {
+       my ($vmid, $resp) = @_;
+       my $data = $resp->{'return'} || [];
+
+       $res->{$vmid}->{'running-machine'} =
+           PVE::QemuServer::Machine::current_from_query_machines($data);
+    };
+
+    my $versioncb = sub {
+       my ($vmid, $resp) = @_;
+       my $data = $resp->{'return'} // {};
+       my $version = 'unknown';
+
+       if (my $v = $data->{qemu}) {
+           $version = $v->{major} . "." . $v->{minor} . "." . $v->{micro};
+       }
+
+       $res->{$vmid}->{'running-qemu'} = $version;
+    };
+
     my $statuscb = sub {
        my ($vmid, $resp) = @_;
 
        $qmpclient->queue_cmd($vmid, $blockstatscb, 'query-blockstats');
+       $qmpclient->queue_cmd($vmid, $machinecb, 'query-machines');
+       $qmpclient->queue_cmd($vmid, $versioncb, 'query-version');
        # this fails if ballon driver is not loaded, so this must be
        # the last commnand (following command are aborted if this fails).
        $qmpclient->queue_cmd($vmid, $ballooncb, 'query-balloon');
@@ -2756,6 +2792,16 @@ sub vmstatus {
 
     $qmpclient->queue_execute(undef, 2);
 
+    foreach my $vmid (keys %$list) {
+       next if $opt_vmid && ($vmid ne $opt_vmid);
+       next if !$res->{$vmid}->{pid}; #not running
+
+       # we can't use the $qmpclient since it might have already aborted on
+       # 'query-balloon', but this might also fail for older versions...
+       my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
+       $res->{$vmid}->{'proxmox-support'} = $qemu_support // {};
+    }
+
     foreach my $vmid (keys %$list) {
        next if $opt_vmid && ($vmid ne $opt_vmid);
        $res->{$vmid}->{qmpstatus} = $res->{$vmid}->{status} if !$res->{$vmid}->{qmpstatus};
@@ -2781,7 +2827,7 @@ sub conf_has_audio {
 
     $id //= 0;
     my $audio = $conf->{"audio$id"};
-    return undef if !defined($audio);
+    return if !defined($audio);
 
     my $audioproperties = parse_property_string($audio_fmt, $audio);
     my $audiodriver = $audioproperties->{driver} // 'spice';
@@ -2845,16 +2891,51 @@ my $default_machines = {
     aarch64 => 'virt',
 };
 
+sub get_installed_machine_version {
+    my ($kvmversion) = @_;
+    $kvmversion = kvm_user_version() if !defined($kvmversion);
+    $kvmversion =~ m/^(\d+\.\d+)/;
+    return $1;
+}
+
+sub windows_get_pinned_machine_version {
+    my ($machine, $base_version, $kvmversion) = @_;
+
+    my $pin_version = $base_version;
+    if (!defined($base_version) ||
+       !PVE::QemuServer::Machine::can_run_pve_machine_version($base_version, $kvmversion)
+    ) {
+       $pin_version = get_installed_machine_version($kvmversion);
+    }
+    if (!$machine || $machine eq 'pc') {
+       $machine = "pc-i440fx-$pin_version";
+    } elsif ($machine eq 'q35') {
+       $machine = "pc-q35-$pin_version";
+    } elsif ($machine eq 'virt') {
+       $machine = "virt-$pin_version";
+    } else {
+       warn "unknown machine type '$machine', not touching that!\n";
+    }
+
+    return $machine;
+}
+
 sub get_vm_machine {
     my ($conf, $forcemachine, $arch, $add_pve_version, $kvmversion) = @_;
 
     my $machine = $forcemachine || $conf->{machine};
 
     if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
+       $kvmversion //= kvm_user_version();
+       # we must pin Windows VMs without a specific version to 5.1, as 5.2 fixed a bug in ACPI
+       # layout which confuses windows quite a bit and may result in various regressions..
+       # see: https://lists.gnu.org/archive/html/qemu-devel/2021-02/msg08484.html
+       if (windows_version($conf->{ostype})) {
+           $machine = windows_get_pinned_machine_version($machine, '5.1', $kvmversion);
+       }
        $arch //= 'x86_64';
        $machine ||= $default_machines->{$arch};
        if ($add_pve_version) {
-           $kvmversion //= kvm_user_version();
            my $pvever = PVE::QemuServer::Machine::get_pve_version($kvmversion);
            $machine .= "+pve$pvever";
        }
@@ -3089,6 +3170,8 @@ sub config_to_command {
 
     push @$cmd, '-name', $vmname;
 
+    push @$cmd, '-no-shutdown';
+
     my $use_virtio = 0;
 
     my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket($vmid);
@@ -3213,17 +3296,20 @@ sub config_to_command {
        push @$devices, '-device', $kbd if defined($kbd);
     }
 
+    my $bootorder = device_bootorder($conf);
+
     # host pci device passthrough
     my ($kvm_off, $gpu_passthrough, $legacy_igd) = PVE::QemuServer::PCI::print_hostpci_devices(
-       $vmid, $conf, $devices, $winversion, $q35, $bridges, $arch, $machine_type);
+       $vmid, $conf, $devices, $vga, $winversion, $q35, $bridges, $arch, $machine_type, $bootorder);
 
     # usb devices
     my $usb_dev_features = {};
     $usb_dev_features->{spice_usb3} = 1 if min_version($machine_version, 4, 0);
 
     my @usbdevices = PVE::QemuServer::USB::get_usb_devices(
-        $conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features);
+        $conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features, $bootorder);
     push @$devices, @usbdevices if @usbdevices;
+
     # serial devices
     for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++)  {
        if (my $path = $conf->{"serial$i"}) {
@@ -3291,15 +3377,6 @@ sub config_to_command {
     }
     push @$cmd, '-nodefaults';
 
-    my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
-
-    my $bootindex_hash = {};
-    my $i = 1;
-    foreach my $o (split(//, $bootorder)) {
-       $bootindex_hash->{$o} = $i*100;
-       $i++;
-    }
-
     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;
@@ -3369,8 +3446,8 @@ sub config_to_command {
        }
     }
 
-    my $rng = parse_rng($conf->{rng0}) if $conf->{rng0};
-    if ($rng && &$version_guard(4, 1, 2)) {
+    my $rng = $conf->{rng0} ? parse_rng($conf->{rng0}) : undef;
+    if ($rng && $version_guard->(4, 1, 2)) {
        check_rng_source($rng->{source});
 
        my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
@@ -3469,17 +3546,7 @@ sub config_to_command {
 
        $use_virtio = 1 if $ds =~ m/^virtio/;
 
-       if (drive_is_cdrom ($drive)) {
-           if ($bootindex_hash->{d}) {
-               $drive->{bootindex} = $bootindex_hash->{d};
-               $bootindex_hash->{d} += 1;
-           }
-       } else {
-           if ($bootindex_hash->{c}) {
-               $drive->{bootindex} = $bootindex_hash->{c} if $conf->{bootdisk} && ($conf->{bootdisk} eq $ds);
-               $bootindex_hash->{c} += 1;
-           }
-       }
+       $drive->{bootindex} = $bootorder->{$ds} if $bootorder->{$ds};
 
        if ($drive->{interface} eq 'virtio'){
            push @$cmd, '-object', "iothread,id=iothread-$ds" if $drive->{iothread};
@@ -3530,22 +3597,21 @@ sub config_to_command {
     });
 
     for (my $i = 0; $i < $MAX_NETS; $i++) {
-       next if !$conf->{"net$i"};
-       my $d = parse_net($conf->{"net$i"});
+       my $netname = "net$i";
+
+       next if !$conf->{$netname};
+       my $d = parse_net($conf->{$netname});
        next if !$d;
 
        $use_virtio = 1 if $d->{model} eq 'virtio';
 
-       if ($bootindex_hash->{n}) {
-           $d->{bootindex} = $bootindex_hash->{n};
-           $bootindex_hash->{n} += 1;
-       }
+       $d->{bootindex} = $bootorder->{$netname} if $bootorder->{$netname};
 
-       my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, "net$i");
+       my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, $netname);
        push @$devices, '-netdev', $netdevfull;
 
        my $netdevicefull = print_netdevice_full(
-           $vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files, $arch, $machine_type);
+           $vmid, $conf, $d, $netname, $bridges, $use_old_bios_files, $arch, $machine_type);
 
        push @$devices, '-device', $netdevicefull;
     }
@@ -3729,17 +3795,16 @@ sub vm_deviceplug {
     } 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));
+       # since we can't reliably hot unplug all added usb devices and usb
+       # passthrough breaks 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);
 
         qemu_driveadd($storecfg, $vmid, $device);
-        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
+        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);
 
         qemu_deviceadd($vmid, $devicefull);
        eval { qemu_deviceaddverify($vmid, $deviceid); };
@@ -3775,7 +3840,7 @@ sub vm_deviceplug {
         qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device, $arch, $machine_type);
         qemu_driveadd($storecfg, $vmid, $device);
 
-       my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
+       my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);
        eval { qemu_deviceadd($vmid, $devicefull); };
        if (my $err = $@) {
            eval { qemu_drivedel($vmid, $deviceid); };
@@ -3785,7 +3850,7 @@ sub vm_deviceplug {
 
     } elsif ($deviceid =~ m/^(net)(\d+)$/) {
 
-       return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
+       return if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
 
        my $machine_type = PVE::QemuServer::Machine::qemu_machine_pxe($vmid, $conf);
        my $use_old_bios_files = undef;
@@ -3827,7 +3892,8 @@ sub vm_deviceunplug {
     my $devices_list = vm_devices_list($vmid);
     return 1 if !defined($devices_list->{$deviceid});
 
-    die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
+    my $bootdisks = PVE::QemuServer::Drive::get_bootdisks($conf);
+    die "can't unplug bootdisk '$deviceid'\n" if grep {$_ eq $deviceid} @$bootdisks;
 
     if ($deviceid eq 'tablet' || $deviceid eq 'keyboard') {
 
@@ -3836,11 +3902,10 @@ sub vm_deviceunplug {
     } 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);
+       # 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+)$/) {
 
@@ -4214,39 +4279,6 @@ sub qemu_block_set_io_throttle {
 
 }
 
-# old code, only used to shutdown old VM after update
-sub __read_avail {
-    my ($fh, $timeout) = @_;
-
-    my $sel = new IO::Select;
-    $sel->add($fh);
-
-    my $res = '';
-    my $buf;
-
-    my @ready;
-    while (scalar (@ready = $sel->can_read($timeout))) {
-       my $count;
-       if ($count = $fh->sysread($buf, 8192)) {
-           if ($buf =~ /^(.*)\(qemu\) $/s) {
-               $res .= $1;
-               last;
-           } else {
-               $res .= $buf;
-           }
-       } else {
-           if (!defined($count)) {
-               die "$!\n";
-           }
-           last;
-       }
-    }
-
-    die "monitor read timeout\n" if !scalar(@ready);
-
-    return $res;
-}
-
 sub qemu_block_resize {
     my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
 
@@ -4300,6 +4332,8 @@ sub qemu_volume_snapshot_delete {
 sub set_migration_caps {
     my ($vmid) = @_;
 
+    my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
+
     my $cap_ref = [];
 
     my $enabled_cap = {
@@ -4307,7 +4341,8 @@ sub set_migration_caps {
        "xbzrle" => 1,
        "x-rdma-pin-all" => 0,
        "zero-blocks" => 0,
-       "compress" => 0
+       "compress" => 0,
+       "dirty-bitmaps" => $qemu_support->{'pbs-dirty-bitmap-migration'} ? 1 : 0,
     };
 
     my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
@@ -4423,6 +4458,7 @@ sub vmconfig_hotplug_pending {
 
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
 
+    my $cgroup = PVE::QemuServer::CGroup->new($vmid);
     my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
     foreach my $opt (sort keys %$pending_delete_hash) {
        next if $selection && !$selection->{$opt};
@@ -4442,10 +4478,9 @@ sub vmconfig_hotplug_pending {
                }
            } 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);
+               # 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);
@@ -4468,9 +4503,9 @@ sub vmconfig_hotplug_pending {
                die "skip\n" if !$hotplug_features->{memory};
                PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt);
            } elsif ($opt eq 'cpuunits') {
-               cgroups_write("cpu", $vmid, "cpu.shares", $defaults->{cpuunits});
+               $cgroup->change_cpu_shares(undef, $defaults->{cpuunits});
            } elsif ($opt eq 'cpulimit') {
-               cgroups_write("cpu", $vmid, "cpu.cfs_quota_us", -1);
+               $cgroup->change_cpu_quota(-1, 100000);
            } else {
                die "skip\n";
            }
@@ -4496,6 +4531,13 @@ sub vmconfig_hotplug_pending {
            $conf->{$opt} = delete $conf->{pending}->{$opt};
        }
 
+       my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
+       foreach my $opt (sort keys %$pending_delete_hash) {
+           next if !grep { $_ eq $opt } @cloudinit_opts;
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
+           delete $conf->{$opt};
+       }
+
        my $new_conf = { %$conf };
        $new_conf->{$key} = $value;
        PVE::QemuServer::Cloudinit::generate_cloudinitconfig($new_conf, $vmid);
@@ -4519,12 +4561,11 @@ sub vmconfig_hotplug_pending {
                }
            } 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 { parse_property_string($usbdesc->{format}, $value) };
-               die "skip\n" if !$d;
-               qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d, $arch, $machine_type);
+               # since we cannot reliably hot unplug usb devices we disable it for now
+               #die "skip\n" if !$hotplug_features->{usb} || $value =~ m/spice/i;
+               #my $d = eval { parse_property_string($usbdesc->{format}, $value) };
+               #die "skip\n" if !$d;
+               #qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d, $arch, $machine_type);
            } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug_features->{cpu};
                qemu_cpu_hotplug($vmid, $conf, $value);
@@ -4556,10 +4597,10 @@ sub vmconfig_hotplug_pending {
                die "skip\n" if !$hotplug_features->{memory};
                $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});
+               $cgroup->change_cpu_shares($conf->{pending}->{$opt}, $defaults->{cpuunits});
            } elsif ($opt eq 'cpulimit') {
                my $cpulimit = $conf->{pending}->{$opt} == 0 ? -1 : int($conf->{pending}->{$opt} * 100000);
-               cgroups_write("cpu", $vmid, "cpu.cfs_quota_us", $cpulimit);
+               $cgroup->change_cpu_quota($cpulimit, 100000);
            } else {
                die "skip\n";  # skip non-hot-pluggable options
            }
@@ -4595,7 +4636,7 @@ sub try_deallocate_drive {
        }
     }
 
-    return undef;
+    return;
 }
 
 sub vmconfig_delete_or_detach_drive {
@@ -4910,13 +4951,22 @@ sub vm_start {
            if !$params->{skiptemplate} && PVE::QemuConfig->is_template($conf);
 
        my $has_suspended_lock = PVE::QemuConfig->has_lock($conf, 'suspended');
+       my $has_backup_lock = PVE::QemuConfig->has_lock($conf, 'backup');
+
+       my $running = check_running($vmid, undef, $migrate_opts->{migratedfrom});
+
+       if ($has_backup_lock && $running) {
+           # a backup is currently running, attempt to start the guest in the
+           # existing QEMU instance
+           return vm_resume($vmid);
+       }
 
        PVE::QemuConfig->check_lock($conf)
            if !($params->{skiplock} || $has_suspended_lock);
 
        $params->{resume} = $has_suspended_lock || defined($conf->{vmstate});
 
-       die "VM $vmid already running\n" if check_running($vmid, undef, $migrate_opts->{migratedfrom});
+       die "VM $vmid already running\n" if $running;
 
        if (my $storagemap = $migrate_opts->{storagemap}) {
            my $replicated = $migrate_opts->{replicated_volumes};
@@ -5087,10 +5137,10 @@ sub vm_start_nolock {
                my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
                PVE::SysFSTools::pci_create_mdev_device($pciid, $uuid, $d->{mdev});
            } else {
-               die "can't unbind/bind pci group to vfio '$pciid'\n"
+               die "can't unbind/bind PCI group to VFIO '$pciid'\n"
                    if !PVE::SysFSTools::pci_dev_group_bind_to_vfio($pciid);
-               die "can't reset pci device '$pciid'\n"
-                   if $info->{has_fl_reset} and !PVE::SysFSTools::pci_dev_reset($info);
+               die "can't reset PCI device '$pciid'\n"
+                   if $info->{has_fl_reset} && !PVE::SysFSTools::pci_dev_reset($info);
            }
       }
     }
@@ -5124,10 +5174,15 @@ sub vm_start_nolock {
 
     my %properties = (
        Slice => 'qemu.slice',
-       KillMode => 'none',
-       CPUShares => $cpuunits
+       KillMode => 'none'
     );
 
+    if (PVE::CGroup::cgroup_mode() == 2) {
+       $properties{CPUWeight} = $cpuunits;
+    } else {
+       $properties{CPUShares} = $cpuunits;
+    }
+
     if (my $cpulimit = $conf->{cpulimit}) {
        $properties{CPUQuota} = int($cpulimit * 100);
     }
@@ -5390,7 +5445,7 @@ sub _do_vm_stop {
 
     eval {
        if ($shutdown) {
-           if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
+           if (defined($conf) && get_qga_key($conf, 'enabled')) {
                mon_cmd($vmid, "guest-shutdown", timeout => $timeout);
            } else {
                mon_cmd($vmid, "system_powerdown");
@@ -5596,9 +5651,12 @@ sub vm_resume {
     PVE::QemuConfig->lock_config($vmid, sub {
        my $res = mon_cmd($vmid, 'query-status');
        my $resume_cmd = 'cont';
+       my $reset = 0;
 
-       if ($res->{status} && $res->{status} eq 'suspended') {
-           $resume_cmd = 'system_wakeup';
+       if ($res->{status}) {
+           return if $res->{status} eq 'running'; # job done, go home
+           $resume_cmd = 'system_wakeup' if $res->{status} eq 'suspended';
+           $reset = 1 if $res->{status} eq 'shutdown';
        }
 
        if (!$nocheck) {
@@ -5609,6 +5667,11 @@ sub vm_resume {
                if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
        }
 
+       if ($reset) {
+           # required if a VM shuts down during a backup and we get a resume
+           # request before the backup finishes for example
+           mon_cmd($vmid, "system_reset");
+       }
        mon_cmd($vmid, $resume_cmd);
     });
 }
@@ -5812,7 +5875,10 @@ my $restore_allocate_devices = sub {
        my $name;
        if ($d->{is_cloudinit}) {
            $name = "vm-$vmid-cloudinit";
-           $name .= ".$d->{format}" if $d->{format} ne 'raw';
+           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+           if ($scfg->{path}) {
+               $name .= ".$d->{format}";
+           }
        }
 
        my $volid = PVE::Storage::vdisk_alloc(
@@ -5830,13 +5896,15 @@ my $restore_allocate_devices = sub {
 };
 
 my $restore_update_config_line = sub {
-    my ($outfd, $cookie, $vmid, $map, $line, $unique) = @_;
+    my ($cookie, $vmid, $map, $line, $unique) = @_;
+
+    return '' if $line =~ m/^\#qmdump\#/;
+    return '' if $line =~ m/^\#vzdump\#/;
+    return '' if $line =~ m/^lock:/;
+    return '' if $line =~ m/^unused\d+:/;
+    return '' if $line =~ m/^parent:/;
 
-    return if $line =~ m/^\#qmdump\#/;
-    return if $line =~ m/^\#vzdump\#/;
-    return if $line =~ m/^lock:/;
-    return if $line =~ m/^unused\d+:/;
-    return if $line =~ m/^parent:/;
+    my $res = '';
 
     my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
     if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
@@ -5852,7 +5920,7 @@ my $restore_update_config_line = sub {
            };
            my $netstr = print_net($net);
 
-           print $outfd "net$cookie->{netcount}: $netstr\n";
+           $res .= "net$cookie->{netcount}: $netstr\n";
            $cookie->{netcount}++;
        }
     } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && $unique) {
@@ -5860,20 +5928,20 @@ my $restore_update_config_line = sub {
        my $net = parse_net($netstr);
        $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if $net->{macaddr};
        $netstr = print_net($net);
-       print $outfd "$id: $netstr\n";
+       $res .= "$id: $netstr\n";
     } elsif ($line =~ m/^((ide|scsi|virtio|sata|efidisk)\d+):\s*(\S+)\s*$/) {
        my $virtdev = $1;
        my $value = $3;
        my $di = parse_drive($virtdev, $value);
        if (defined($di->{backup}) && !$di->{backup}) {
-           print $outfd "#$line";
+           $res .= "#$line";
        } elsif ($map->{$virtdev}) {
            delete $di->{format}; # format can change on restore
            $di->{file} = $map->{$virtdev};
            $value = print_drive($di);
-           print $outfd "$virtdev: $value\n";
+           $res .= "$virtdev: $value\n";
        } else {
-           print $outfd $line;
+           $res .= $line;
        }
     } elsif (($line =~ m/^vmgenid: (.*)/)) {
        my $vmgenid = $1;
@@ -5881,17 +5949,19 @@ my $restore_update_config_line = sub {
            # always generate a new vmgenid if there was a valid one setup
            $vmgenid = generate_uuid();
        }
-       print $outfd "vmgenid: $vmgenid\n";
+       $res .= "vmgenid: $vmgenid\n";
     } 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";
+       $res .= $1.print_smbios1($smbios1)."\n";
     } else {
-       print $outfd $line;
+       $res .= $line;
     }
+
+    return $res;
 };
 
 my $restore_deactivate_volumes = sub {
@@ -5988,7 +6058,8 @@ sub update_disk_config {
        my $volid = $drive->{file};
        return if !$volid;
 
-       my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};
+       my $path;
+       $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};
        if ($referenced->{$volid} || ($path && $referencedpath->{$path})) {
            print "$prefix remove entry '$opt', its volume '$volid' is in use\n";
            $changes = 1;
@@ -6072,13 +6143,10 @@ sub restore_proxmox_backup_archive {
     my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive);
     my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
 
-    my $server = $scfg->{server};
-    my $datastore = $scfg->{datastore};
-    my $username = $scfg->{username} // 'root@pam';
     my $fingerprint = $scfg->{fingerprint};
     my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);
 
-    my $repo = "$username\@$server:$datastore";
+    my $repo = PVE::PBSClient::get_repository($scfg);
 
     # This is only used for `pbs-restore`!
     my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
@@ -6097,7 +6165,6 @@ sub restore_proxmox_backup_archive {
     mkpath $tmpdir;
 
     my $conffile = PVE::QemuConfig->config_file($vmid);
-    my $tmpfn = "$conffile.$$.tmp";
      # disable interrupts (always do cleanups)
     local $SIG{INT} =
        local $SIG{TERM} =
@@ -6107,6 +6174,7 @@ sub restore_proxmox_backup_archive {
     # Note: $oldconf is undef if VM does not exists
     my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
     my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+    my $new_conf_raw = '';
 
     my $rpcenv = PVE::RPCEnvironment::get();
     my $devinfo = {};
@@ -6209,16 +6277,18 @@ sub restore_proxmox_backup_archive {
 
        $fh->seek(0, 0) || die "seek failed - $!\n";
 
-       my $outfd = new IO::File ($tmpfn, "w") ||
-           die "unable to write config for VM $vmid\n";
-
        my $cookie = { netcount => 0 };
        while (defined(my $line = <$fh>)) {
-           $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $options->{unique});
+           $new_conf_raw .= $restore_update_config_line->(
+               $cookie,
+               $vmid,
+               $map,
+               $line,
+               $options->{unique},
+           );
        }
 
        $fh->close();
-       $outfd->close();
     };
     my $err = $@;
 
@@ -6227,13 +6297,11 @@ sub restore_proxmox_backup_archive {
     rmtree $tmpdir;
 
     if ($err) {
-       unlink $tmpfn;
        $restore_destroy_volumes->($storecfg, $devinfo);
        die $err;
     }
 
-    rename($tmpfn, $conffile) ||
-       die "unable to commit configuration file '$conffile'\n";
+    PVE::Tools::file_set_contents($conffile, $new_conf_raw);
 
     PVE::Cluster::cfs_update(); # make sure we read new file
 
@@ -6296,10 +6364,7 @@ sub restore_vma_archive {
     my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
     POSIX::mkfifo($mapfifo, 0600);
     my $fifofh;
-
-    my $openfifo = sub {
-       open($fifofh, '>', $mapfifo) || die $!;
-    };
+    my $openfifo = sub { open($fifofh, '>', $mapfifo) or die $! };
 
     $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
 
@@ -6311,11 +6376,11 @@ sub restore_vma_archive {
     my $rpcenv = PVE::RPCEnvironment::get();
 
     my $conffile = PVE::QemuConfig->config_file($vmid);
-    my $tmpfn = "$conffile.$$.tmp";
 
     # Note: $oldconf is undef if VM does not exist
     my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
     my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+    my $new_conf_raw = '';
 
     my %storage_limits;
 
@@ -6383,16 +6448,18 @@ sub restore_vma_archive {
 
        $fh->seek(0, 0) || die "seek failed - $!\n";
 
-       my $outfd = new IO::File ($tmpfn, "w") ||
-           die "unable to write config for VM $vmid\n";
-
        my $cookie = { netcount => 0 };
        while (defined(my $line = <$fh>)) {
-           $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+           $new_conf_raw .= $restore_update_config_line->(
+               $cookie,
+               $vmid,
+               $map,
+               $line,
+               $opts->{unique},
+           );
        }
 
        $fh->close();
-       $outfd->close();
     };
 
     eval {
@@ -6425,6 +6492,7 @@ sub restore_vma_archive {
                $oldtimeout = undef;
                alarm($tmp);
                close($fifofh);
+               $fifofh = undef;
            }
        };
 
@@ -6437,17 +6505,16 @@ sub restore_vma_archive {
 
     $restore_deactivate_volumes->($cfg, $devinfo);
 
+    close($fifofh) if $fifofh;
     unlink $mapfifo;
     rmtree $tmpdir;
 
     if ($err) {
-       unlink $tmpfn;
        $restore_destroy_volumes->($cfg, $devinfo);
        die $err;
     }
 
-    rename($tmpfn, $conffile) ||
-       die "unable to commit configuration file '$conffile'\n";
+    PVE::Tools::file_set_contents($conffile, $new_conf_raw);
 
     PVE::Cluster::cfs_update(); # make sure we read new file
 
@@ -6460,7 +6527,7 @@ sub restore_tar_archive {
 
     if ($archive ne '-') {
        my $firstfile = tar_archive_read_firstfile($archive);
-       die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"
+       die "ERROR: file '$archive' does not look like a QemuServer vzdump backup\n"
            if $firstfile ne 'qemu-server.conf';
     }
 
@@ -6492,7 +6559,7 @@ sub restore_tar_archive {
     local $ENV{VZDUMP_USER} = $user;
 
     my $conffile = PVE::QemuConfig->config_file($vmid);
-    my $tmpfn = "$conffile.$$.tmp";
+    my $new_conf_raw = '';
 
     # disable interrupts (always do cleanups)
     local $SIG{INT} =
@@ -6534,30 +6601,29 @@ sub restore_tar_archive {
 
        my $confsrc = "$tmpdir/qemu-server.conf";
 
-       my $srcfd = new IO::File($confsrc, "r") ||
-           die "unable to open file '$confsrc'\n";
-
-       my $outfd = new IO::File ($tmpfn, "w") ||
-           die "unable to write config for VM $vmid\n";
+       my $srcfd = IO::File->new($confsrc, "r") || die "unable to open file '$confsrc'\n";
 
        my $cookie = { netcount => 0 };
        while (defined (my $line = <$srcfd>)) {
-           $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+           $new_conf_raw .= $restore_update_config_line->(
+               $cookie,
+               $vmid,
+               $map,
+               $line,
+               $opts->{unique},
+           );
        }
 
        $srcfd->close();
-       $outfd->close();
     };
     if (my $err = $@) {
-       unlink $tmpfn;
        tar_restore_cleanup($storecfg, "$tmpdir/qmrestore.stat") if !$opts->{info};
        die $err;
     }
 
     rmtree $tmpdir;
 
-    rename $tmpfn, $conffile ||
-       die "unable to commit configuration file '$conffile'\n";
+    PVE::Tools::file_set_contents($conffile, $new_conf_raw);
 
     PVE::Cluster::cfs_update(); # make sure we read new file
 
@@ -6593,6 +6659,7 @@ sub do_snapshots_with_qemu {
 
     my $storage_name = PVE::Storage::parse_volume_id($volid);
     my $scfg = $storecfg->{ids}->{$storage_name};
+    die "could not find storage '$storage_name'\n" if !defined($scfg);
 
     if ($qemu_snap_storage->{$scfg->{type}} && !$scfg->{krbd}){
        return 1;
@@ -6602,7 +6669,7 @@ sub do_snapshots_with_qemu {
        return 1;
     }
 
-    return undef;
+    return;
 }
 
 sub qga_check_running {
@@ -6957,25 +7024,37 @@ sub clone_disk {
        $storeid = $storage if $storage;
 
        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";
        my $name = undef;
+       my $size = undef;
        if (drive_is_cloudinit($drive)) {
            $name = "vm-$newvmid-cloudinit";
-           $name .= ".$dst_format" if $dst_format ne 'raw';
+           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+           if ($scfg->{path}) {
+               $name .= ".$dst_format";
+           }
            $snapname = undef;
            $size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE;
        } elsif ($drivename eq 'efidisk0') {
            $size = get_efivars_size($conf);
+       } else {
+           ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 10);
        }
-       $size /= 1024;
-       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, $size);
+       $newvolid = PVE::Storage::vdisk_alloc(
+           $storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024)
+       );
        push @$newvollist, $newvolid;
 
        PVE::Storage::activate_volumes($storecfg, [$newvolid]);
 
        if (drive_is_cloudinit($drive)) {
+           # when cloning multiple disks (e.g. during clone_vm) it might be the last disk
+           # if this is the case, we have to complete any block-jobs still there from
+           # previous drive-mirrors
+           if (($completion eq 'complete') && (scalar(keys %$jobs) > 0)) {
+               qemu_drive_mirror_monitor($vmid, $newvmid, $jobs, $completion, $qga);
+           }
            goto no_data_clone;
        }
 
@@ -6988,7 +7067,11 @@ sub clone_disk {
                # that is given by the OVMF_VARS.fd
                my $src_path = PVE::Storage::path($storecfg, $drive->{file});
                my $dst_path = PVE::Storage::path($storecfg, $newvolid);
-               run_command(['qemu-img', 'dd', '-n', '-O', $dst_format, "bs=1", "count=$size",
+
+               # better for Ceph if block size is not too small, see bug #3324
+               my $bs = 1024*1024;
+
+               run_command(['qemu-img', 'dd', '-n', '-O', $dst_format, "bs=$bs", "osize=$size",
                    "if=$src_path", "of=$dst_path"]);
            } else {
                qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
@@ -7007,12 +7090,12 @@ sub clone_disk {
     }
 
 no_data_clone:
-    my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
+    my ($size) = eval { PVE::Storage::volume_size_info($storecfg, $newvolid, 10) };
 
     my $disk = $drive;
     $disk->{format} = undef;
     $disk->{file} = $newvolid;
-    $disk->{size} = $size;
+    $disk->{size} = $size if defined($size);
 
     return $disk;
 }
@@ -7282,6 +7365,26 @@ sub get_default_bootdevices {
     return \@ret;
 }
 
+sub device_bootorder {
+    my ($conf) = @_;
+
+    return bootorder_from_legacy($conf) if !defined($conf->{boot});
+
+    my $boot = parse_property_string($boot_fmt, $conf->{boot});
+
+    my $bootorder = {};
+    if (!defined($boot) || $boot->{legacy}) {
+       $bootorder = bootorder_from_legacy($conf, $boot);
+    } elsif ($boot->{order}) {
+       my $i = 100; # start at 100 to allow user to insert devices before us with -args
+       for my $dev (PVE::Tools::split_list($boot->{order})) {
+           $bootorder->{$dev} = $i++;
+       }
+    }
+
+    return $bootorder;
+}
+
 # bash completion helper
 
 sub complete_backup_archives {
@@ -7373,4 +7476,14 @@ sub complete_migration_storage {
     return $res;
 }
 
+sub vm_is_paused {
+    my ($vmid) = @_;
+    my $qmpstatus = eval {
+       PVE::QemuConfig::assert_config_exists_on_node($vmid);
+       mon_cmd($vmid, "query-status");
+    };
+    warn "$@\n" if $@;
+    return $qmpstatus && $qmpstatus->{status} eq "paused";
+}
+
 1;