]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
cfg2cmd: fix +pveN machine types with pxe
[qemu-server.git] / PVE / QemuServer.pm
index cfac03a92858236763b8f9637efd04af2f94f8b8..72a4d46da72d1137de7b09ae76e30fd9894be7e3 100644 (file)
@@ -27,12 +27,15 @@ 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::Format qw(render_duration render_bytes);
 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 +46,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 +125,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 +157,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 +212,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 +385,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 +393,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 +719,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,
@@ -970,7 +965,8 @@ IP addresses use CIDR notation, gateways are optional but need an IP of the same
 
 The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit
 gateway should be provided.
-For IPv6 the special string 'auto' can be used to use stateless autoconfiguration.
+For IPv6 the special string 'auto' can be used to use stateless autoconfiguration. This requires
+cloud-init 19.4 or newer.
 
 If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using
 dhcp on IPv4.
@@ -998,7 +994,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 +1139,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 +1155,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 +1239,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 +1315,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 +1330,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 +1352,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 +1374,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,18 +1400,23 @@ 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";
 }
 
+my sub get_drive_id {
+    my ($drive) = @_;
+    return "$drive->{interface}$drive->{index}";
+}
+
 sub print_drivedevice_full {
     my ($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type) = @_;
 
     my $device = '';
     my $maxdev = 0;
 
-    my $drive_id = "$drive->{interface}$drive->{index}";
+    my $drive_id = get_drive_id($drive);
     if ($drive->{interface} eq 'virtio') {
        my $pciaddr = print_pci_addr("$drive_id", $bridges, $arch, $machine_type);
        $device = "virtio-blk-pci,drive=drive-$drive_id,id=${drive_id}${pciaddr}";
@@ -1507,7 +1509,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;
@@ -1519,28 +1521,32 @@ sub get_initiator_name {
 }
 
 sub print_drive_commandline_full {
-    my ($storecfg, $vmid, $drive) = @_;
+    my ($storecfg, $vmid, $drive, $pbs_name) = @_;
 
     my $path;
     my $volid = $drive->{file};
-    my $format;
+    my $format = $drive->{format};
+    my $drive_id = get_drive_id($drive);
 
     if (drive_is_cdrom($drive)) {
        $path = get_iso_path($storecfg, $vmid, $volid);
+        die "$drive_id: cannot back cdrom drive with PBS snapshot\n" if $pbs_name;
     } else {
        my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
        if ($storeid) {
            $path = PVE::Storage::path($storecfg, $volid);
            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-           $format = qemu_img_format($scfg, $volname);
+           $format //= qemu_img_format($scfg, $volname);
        } else {
            $path = $volid;
-           $format = "raw";
+           $format //= "raw";
        }
    }
 
+   my $is_rbd = $path =~ m/^rbd:/;
+
     my $opts = '';
-    my @qemu_drive_options = qw(heads secs cyls trans media format cache rerror werror aio discard);
+    my @qemu_drive_options = qw(heads secs cyls trans media cache rerror werror aio discard);
     foreach my $o (@qemu_drive_options) {
        $opts .= ",$o=$drive->{$o}" if defined($drive->{$o});
     }
@@ -1573,7 +1579,14 @@ sub print_drive_commandline_full {
        }
     }
 
-    $opts .= ",format=$format" if $format && !$drive->{format};
+    if ($pbs_name) {
+       $format = "rbd" if $is_rbd;
+       die "$drive_id: Proxmox Backup Server backed drive cannot auto-detect the format\n"
+           if !$format;
+       $opts .= ",format=alloc-track,file.driver=$format";
+    } elsif ($format) {
+       $opts .= ",format=$format";
+    }
 
     my $cache_direct = 0;
 
@@ -1603,19 +1616,44 @@ sub print_drive_commandline_full {
            # This used to be our default with discard not being specified:
            $detectzeroes = 'on';
        }
-       $opts .= ",detect-zeroes=$detectzeroes" if $detectzeroes;
+
+       # note: 'detect-zeroes' works per blockdev and we want it to persist
+       # after the alloc-track is removed, so put it on 'file' directly
+       my $dz_param = $pbs_name ? "file.detect-zeroes" : "detect-zeroes";
+       $opts .= ",$dz_param=$detectzeroes" if $detectzeroes;
     }
 
-    my $pathinfo = $path ? "file=$path," : '';
+    if ($pbs_name) {
+       $opts .= ",backing=$pbs_name";
+       $opts .= ",auto-remove=on";
+    }
+
+    # my $file_param = $pbs_name ? "file.file.filename" : "file";
+    my $file_param = "file";
+    if ($pbs_name) {
+       # non-rbd drivers require the underlying file to be a seperate block
+       # node, so add a second .file indirection
+       $file_param .= ".file" if !$is_rbd;
+       $file_param .= ".filename";
+    }
+    my $pathinfo = $path ? "$file_param=$path," : '';
 
     return "${pathinfo}if=none,id=drive-$drive->{interface}$drive->{index}$opts";
 }
 
+sub print_pbs_blockdev {
+    my ($pbs_conf, $pbs_name) = @_;
+    my $blockdev = "driver=pbs,node-name=$pbs_name,read-only=on";
+    $blockdev .= ",repository=$pbs_conf->{repository}";
+    $blockdev .= ",snapshot=$pbs_conf->{snapshot}";
+    $blockdev .= ",archive=$pbs_conf->{archive}";
+    $blockdev .= ",keyfile=$pbs_conf->{keyfile}" if $pbs_conf->{keyfile};
+    return $blockdev;
+}
+
 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 +1839,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 +1855,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 +1911,7 @@ sub vm_is_volid_owner {
        }
     }
 
-    return undef;
+    return;
 }
 
 sub vmconfig_register_unused_drive {
@@ -1966,7 +2004,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 +2012,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 +2024,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 +2044,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 +2057,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 +2119,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 +2140,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 +2155,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 +2174,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 +2260,8 @@ sub parse_vm_config {
 
                $conf->{$key} = $value;
            }
+       } else {
+           warn "vm $vmid - unable to parse config: $line\n";
        }
     }
 
@@ -2553,6 +2602,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 +2790,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 +2837,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 +2872,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,25 +2936,65 @@ 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";
        }
     }
 
-    if ($add_pve_version && $machine !~ m/\+pve\d+$/) {
+    if ($add_pve_version && $machine !~ m/\+pve\d+?(?:\.pxe)?$/) {
+       my $is_pxe = $machine =~ m/^(.*?)\.pxe$/;
+       $machine = $1 if $is_pxe;
+
        # for version-pinned machines that do not include a pve-version (e.g.
        # pc-q35-4.1), we assume 0 to keep them stable in case we bump
        $machine .= '+pve0';
+
+       $machine .= '.pxe' if $is_pxe;
     }
 
     return $machine;
@@ -3011,7 +3142,8 @@ sub query_understood_cpu_flags {
 }
 
 sub config_to_command {
-    my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu) = @_;
+    my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu,
+        $pbs_backing) = @_;
 
     my $cmd = [];
     my $globalFlags = [];
@@ -3089,6 +3221,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 +3347,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 +3428,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 +3497,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 +3597,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};
@@ -3521,7 +3639,14 @@ sub config_to_command {
            $ahcicontroller->{$controller}=1;
         }
 
-       my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive);
+       my $pbs_conf = $pbs_backing->{$ds};
+       my $pbs_name = undef;
+       if ($pbs_conf) {
+           $pbs_name = "drive-$ds-pbs";
+           push @$devices, '-blockdev', print_pbs_blockdev($pbs_conf, $pbs_name);
+       }
+
+       my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive, $pbs_name);
        $drive_cmd .= ',readonly' if PVE::QemuConfig->is_template($conf);
 
        push @$devices, '-drive',$drive_cmd;
@@ -3530,22 +3655,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 +3853,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 +3898,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 +3908,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 +3950,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 +3960,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 +4337,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) = @_;
 
@@ -4259,8 +4349,13 @@ sub qemu_block_resize {
     my $padding = (1024 - $size % 1024) % 1024;
     $size = $size + $padding;
 
-    mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
-
+    mon_cmd(
+       $vmid,
+       "block_resize",
+       device => $deviceid,
+       size => int($size),
+       timeout => 60,
+    );
 }
 
 sub qemu_volume_snapshot {
@@ -4298,7 +4393,12 @@ sub qemu_volume_snapshot_delete {
 }
 
 sub set_migration_caps {
-    my ($vmid) = @_;
+    my ($vmid, $savevm) = @_;
+
+    my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
+
+    my $bitmap_prop = $savevm ? 'pbs-dirty-bitmap-savevm' : 'pbs-dirty-bitmap-migration';
+    my $dirty_bitmaps = $qemu_support->{$bitmap_prop} ? 1 : 0;
 
     my $cap_ref = [];
 
@@ -4307,7 +4407,8 @@ sub set_migration_caps {
        "xbzrle" => 1,
        "x-rdma-pin-all" => 0,
        "zero-blocks" => 0,
-       "compress" => 0
+       "compress" => 0,
+       "dirty-bitmaps" => $dirty_bitmaps,
     };
 
     my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
@@ -4423,6 +4524,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 +4544,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 +4569,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 +4597,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 +4627,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 +4663,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 +4702,7 @@ sub try_deallocate_drive {
        }
     }
 
-    return undef;
+    return;
 }
 
 sub vmconfig_delete_or_detach_drive {
@@ -4910,13 +5017,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};
@@ -4942,6 +5058,15 @@ sub vm_start {
 #   timeout => in seconds
 #   paused => start VM in paused state (backup)
 #   resume => resume from hibernation
+#   pbs-backing => {
+#      sata0 => {
+#         repository
+#         snapshot
+#         keyfile
+#         archive
+#      },
+#      virtio2 => ...
+#   }
 # migrate_opts:
 #   nbd => volumes for NBD exports (vm_migrate_alloc_nbd_disks)
 #   migratedfrom => source node
@@ -4988,8 +5113,8 @@ sub vm_start_nolock {
        print "Resuming suspended VM\n";
     }
 
-    my ($cmd, $vollist, $spice_port) =
-       config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu);
+    my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid,
+       $conf, $defaults, $forcemachine, $forcecpu, $params->{'pbs-backing'});
 
     my $migration_ip;
     my $get_migration_ip = sub {
@@ -5087,10 +5212,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 +5249,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 +5520,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");
@@ -5545,6 +5675,7 @@ sub vm_suspend {
        PVE::Storage::activate_volumes($storecfg, [$vmstate]);
 
        eval {
+           set_migration_caps($vmid, 1);
            mon_cmd($vmid, "savevm-start", statefile => $path);
            for(;;) {
                my $state = mon_cmd($vmid, "query-savevm");
@@ -5596,9 +5727,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 +5743,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 +5951,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 +5972,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 +5996,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 +6004,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 +6025,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 +6134,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,15 +6219,12 @@ 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`!
+    # This is only used for `pbs-restore` and the QEMU PBS driver (live-restore)
     my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
     local $ENV{PBS_PASSWORD} = $password;
     local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);
@@ -6097,7 +6241,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 +6250,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 = {};
@@ -6177,68 +6321,163 @@ sub restore_proxmox_backup_archive {
        # allocate volumes
        my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid);
 
-       foreach my $virtdev (sort keys %$virtdev_hash) {
-           my $d = $virtdev_hash->{$virtdev};
-           next if $d->{is_cloudinit}; # no need to restore cloudinit
+       if (!$options->{live}) {
+           foreach my $virtdev (sort keys %$virtdev_hash) {
+               my $d = $virtdev_hash->{$virtdev};
+               next if $d->{is_cloudinit}; # no need to restore cloudinit
 
-           my $volid = $d->{volid};
+               my $volid = $d->{volid};
 
-           my $path = PVE::Storage::path($storecfg, $volid);
+               my $path = PVE::Storage::path($storecfg, $volid);
 
-           # This is the ONLY user of the PBS_ env vars set on top of this function!
-           my $pbs_restore_cmd = [
-               '/usr/bin/pbs-restore',
-               '--repository', $repo,
-               $pbs_backup_name,
-               "$d->{devname}.img.fidx",
-               $path,
-               '--verbose',
-               ];
+               my $pbs_restore_cmd = [
+                   '/usr/bin/pbs-restore',
+                   '--repository', $repo,
+                   $pbs_backup_name,
+                   "$d->{devname}.img.fidx",
+                   $path,
+                   '--verbose',
+                   ];
 
-           push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
-           push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
+               push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+               push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
 
-           if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
-               push @$pbs_restore_cmd, '--skip-zero';
-           }
+               if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
+                   push @$pbs_restore_cmd, '--skip-zero';
+               }
 
-           my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);
-           print "restore proxmox backup image: $dbg_cmdstring\n";
-           run_command($pbs_restore_cmd);
+               my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);
+               print "restore proxmox backup image: $dbg_cmdstring\n";
+               run_command($pbs_restore_cmd);
+           }
        }
 
        $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 = $@;
 
-    $restore_deactivate_volumes->($storecfg, $devinfo);
+    if ($err || !$options->{live}) {
+       $restore_deactivate_volumes->($storecfg, $devinfo);
+    }
 
     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
 
     eval { rescan($vmid, 1); };
     warn $@ if $@;
+
+    PVE::AccessControl::add_vm_to_pool($vmid, $options->{pool}) if $options->{pool};
+
+    if ($options->{live}) {
+       eval {
+           # enable interrupts
+           local $SIG{INT} =
+               local $SIG{TERM} =
+               local $SIG{QUIT} =
+               local $SIG{HUP} =
+               local $SIG{PIPE} = sub { die "got signal ($!) - abort\n"; };
+
+           my $conf = PVE::QemuConfig->load_config($vmid);
+           die "cannot do live-restore for template\n" if PVE::QemuConfig->is_template($conf);
+
+           pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $repo, $keyfile, $pbs_backup_name);
+       };
+
+       $err = $@;
+       if ($err) {
+           warn "destroying partially live-restored VM, all temporary data will be lost!\n";
+           $restore_deactivate_volumes->($storecfg, $devinfo);
+           $restore_destroy_volumes->($storecfg, $devinfo);
+           PVE::QemuConfig->destroy_config($vmid);
+           die $err;
+       }
+    }
+}
+
+sub pbs_live_restore {
+    my ($vmid, $conf, $storecfg, $restored_disks, $repo, $keyfile, $snap) = @_;
+
+    print "Starting VM for live-restore\n";
+
+    my $pbs_backing = {};
+    for my $ds (keys %$restored_disks) {
+       $ds =~ m/^drive-(.*)$/;
+       $pbs_backing->{$1} = {
+           repository => $repo,
+           snapshot => $snap,
+           archive => "$ds.img.fidx",
+       };
+       $pbs_backing->{$1}->{keyfile} = $keyfile if -e $keyfile;
+    }
+
+    my $drives_streamed = 0;
+    eval {
+       # make sure HA doesn't interrupt our restore by stopping the VM
+       if (PVE::HA::Config::vm_is_ha_managed($vmid)) {
+           run_command(['ha-manager', 'set',  "vm:$vmid", '--state', 'started']);
+       }
+
+       # start VM with backing chain pointing to PBS backup, environment vars for PBS driver
+       # in QEMU (PBS_PASSWORD and PBS_FINGERPRINT) are already set by our caller
+       vm_start_nolock($storecfg, $vmid, $conf, {paused => 1, 'pbs-backing' => $pbs_backing}, {});
+
+       my $qmeventd_fd = register_qmeventd_handle($vmid);
+
+       # begin streaming, i.e. data copy from PBS to target disk for every vol,
+       # this will effectively collapse the backing image chain consisting of
+       # [target <- alloc-track -> PBS snapshot] to just [target] (alloc-track
+       # removes itself once all backing images vanish with 'auto-remove=on')
+       my $jobs = {};
+       for my $ds (sort keys %$restored_disks) {
+           my $job_id = "restore-$ds";
+           mon_cmd($vmid, 'block-stream',
+               'job-id' => $job_id,
+               device => "$ds",
+           );
+           $jobs->{$job_id} = {};
+       }
+
+       mon_cmd($vmid, 'cont');
+       qemu_drive_mirror_monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+
+       print "restore-drive jobs finished successfully, removing all tracking block devices"
+           ." to disconnect from Proxmox Backup Server\n";
+
+       for my $ds (sort keys %$restored_disks) {
+           mon_cmd($vmid, 'blockdev-del', 'node-name' => "$ds-pbs");
+       }
+
+       close($qmeventd_fd);
+    };
+
+    my $err = $@;
+
+    if ($err) {
+       warn "An error occured during live-restore: $err\n";
+       _do_vm_stop($storecfg, $vmid, 1, 1, 10, 0, 1);
+       die "live-restore failed\n";
+    }
 }
 
 sub restore_vma_archive {
@@ -6296,10 +6535,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 +6547,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;
 
@@ -6335,11 +6571,13 @@ sub restore_vma_archive {
 
        my $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $cfg, $fh, $devinfo, $opts);
 
-       foreach my $key (keys %storage_limits) {
-           my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit);
-           next if !$limit;
-           print STDERR "rate limit for storage $key: $limit KiB/s\n";
-           $storage_limits{$key} = $limit * 1024;
+       foreach my $info (values %{$virtdev_hash}) {
+           my $storeid = $info->{storeid};
+           next if defined($storage_limits{$storeid});
+
+           my $limit = PVE::Storage::get_bandwidth_limit('restore', [$storeid], $bwlimit) // 0;
+           print STDERR "rate limit for storage $storeid: $limit KiB/s\n" if $limit;
+           $storage_limits{$storeid} = $limit * 1024;
        }
 
        foreach my $devname (keys %$devinfo) {
@@ -6383,16 +6621,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 +6665,7 @@ sub restore_vma_archive {
                $oldtimeout = undef;
                alarm($tmp);
                close($fifofh);
+               $fifofh = undef;
            }
        };
 
@@ -6437,22 +6678,23 @@ 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
 
     eval { rescan($vmid, 1); };
     warn $@ if $@;
+
+    PVE::AccessControl::add_vm_to_pool($vmid, $opts->{pool}) if $opts->{pool};
 }
 
 sub restore_tar_archive {
@@ -6460,7 +6702,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 +6734,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 +6776,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 +6834,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 +6844,7 @@ sub do_snapshots_with_qemu {
        return 1;
     }
 
-    return undef;
+    return;
 }
 
 sub qga_check_running {
@@ -6723,9 +6965,10 @@ sub qemu_img_convert {
        if($line =~ m/\((\S+)\/100\%\)/){
            my $percent = $1;
            my $transferred = int($size * $percent / 100);
-           my $remaining = $size - $transferred;
+           my $total_h = render_bytes($size, 1);
+           my $transferred_h = render_bytes($transferred, 1);
 
-           print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
+           print "transferred $transferred_h of $total_h ($percent%)";
        }
 
     };
@@ -6801,55 +7044,81 @@ sub qemu_drive_mirror {
 # 'complete': wait until all jobs are ready, block-job-complete them (default)
 # 'cancel': wait until all jobs are ready, block-job-cancel them
 # 'skip': wait until all jobs are ready, return with block jobs in ready state
+# 'auto': wait until all jobs disappear, only use for jobs which complete automatically
 sub qemu_drive_mirror_monitor {
-    my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_;
+    my ($vmid, $vmiddst, $jobs, $completion, $qga, $op) = @_;
 
     $completion //= 'complete';
+    $op //= "mirror";
 
     eval {
        my $err_complete = 0;
 
+       my $starttime = time ();
        while (1) {
-           die "storage migration timed out\n" if $err_complete > 300;
+           die "block job ('$op') timed out\n" if $err_complete > 300;
 
            my $stats = mon_cmd($vmid, "query-block-jobs");
+           my $ctime = time();
 
-           my $running_mirror_jobs = {};
-           foreach my $stat (@$stats) {
-               next if $stat->{type} ne 'mirror';
-               $running_mirror_jobs->{$stat->{device}} = $stat;
+           my $running_jobs = {};
+           for my $stat (@$stats) {
+               next if $stat->{type} ne $op;
+               $running_jobs->{$stat->{device}} = $stat;
            }
 
            my $readycounter = 0;
 
-           foreach my $job (keys %$jobs) {
+           for my $job_id (sort keys %$jobs) {
+               my $job = $running_jobs->{$job_id};
 
-               if(defined($jobs->{$job}->{complete}) && !defined($running_mirror_jobs->{$job})) {
-                   print "$job : finished\n";
-                   delete $jobs->{$job};
+               my $vanished = !defined($job);
+               my $complete = defined($jobs->{$job_id}->{complete}) && $vanished;
+               if($complete || ($vanished && $completion eq 'auto')) {
+                   print "$job_id: $op-job finished\n";
+                   delete $jobs->{$job_id};
                    next;
                }
 
-               die "$job: mirroring has been cancelled\n" if !defined($running_mirror_jobs->{$job});
+               die "$job_id: '$op' has been cancelled\n" if !defined($job);
 
-               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 $busy = $job->{busy};
+               my $ready = $job->{ready};
+               if (my $total = $job->{len}) {
+                   my $transferred = $job->{offset} || 0;
                    my $remaining = $total - $transferred;
                    my $percent = sprintf "%.2f", ($transferred * 100 / $total);
 
-                   print "$job: transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+                   my $duration = $ctime - $starttime;
+                   my $total_h = render_bytes($total, 1);
+                   my $transferred_h = render_bytes($transferred, 1);
+
+                   my $status = sprintf(
+                       "transferred $transferred_h of $total_h ($percent%%) in %s",
+                       render_duration($duration),
+                   );
+
+                   if ($ready) {
+                       if ($busy) {
+                           $status .= ", still busy"; # shouldn't even happen? but mirror is weird
+                       } else {
+                           $status .= ", ready";
+                       }
+                   }
+                   print "$job_id: $status\n" if !$jobs->{$job_id}->{ready};
+                   $jobs->{$job_id}->{ready} = $ready;
                }
 
-               $readycounter++ if $running_mirror_jobs->{$job}->{ready};
+               $readycounter++ if $job->{ready};
            }
 
            last if scalar(keys %$jobs) == 0;
 
            if ($readycounter == scalar(keys %$jobs)) {
-               print "all mirroring jobs are ready \n";
-               last if $completion eq 'skip'; #do the complete later
+               print "all '$op' jobs are ready\n";
+
+               # do the complete later (or has already been done)
+               last if $completion eq 'skip' || $completion eq 'auto';
 
                if ($vmiddst && $vmiddst != $vmid) {
                    my $agent_running = $qga && qga_check_running($vmid);
@@ -6875,9 +7144,9 @@ sub qemu_drive_mirror_monitor {
                    last;
                } else {
 
-                   foreach my $job (keys %$jobs) {
+                   for my $job_id (sort keys %$jobs) {
                        # try to switch the disk if source and destination are on the same guest
-                       print "$job: Completing block job...\n";
+                       print "$job_id: Completing block job_id...\n";
 
                        my $op;
                        if ($completion eq 'complete') {
@@ -6887,13 +7156,13 @@ sub qemu_drive_mirror_monitor {
                        } else {
                            die "invalid completion value: $completion\n";
                        }
-                       eval { mon_cmd($vmid, $op, device => $job) };
+                       eval { mon_cmd($vmid, $op, device => $job_id) };
                        if ($@ =~ m/cannot be completed/) {
-                           print "$job: Block job cannot be completed, try again.\n";
+                           print "$job_id: block job cannot be completed, trying again.\n";
                            $err_complete++;
                        }else {
-                           print "$job: Completed successfully.\n";
-                           $jobs->{$job}->{complete} = 1;
+                           print "$job_id: Completed successfully.\n";
+                           $jobs->{$job_id}->{complete} = 1;
                        }
                    }
                }
@@ -6905,9 +7174,8 @@ sub qemu_drive_mirror_monitor {
 
     if ($err) {
        eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
-       die "mirroring error: $err";
+       die "block job ($op) error: $err";
     }
-
 }
 
 sub qemu_blockjobs_cancel {
@@ -6957,25 +7225,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 +7268,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 +7291,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 +7566,54 @@ 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;
+}
+
+sub register_qmeventd_handle {
+    my ($vmid) = @_;
+
+    my $fh;
+    my $peer = "/var/run/qmeventd.sock";
+    my $count = 0;
+
+    for (;;) {
+       $count++;
+       $fh = IO::Socket::UNIX->new(Peer => $peer, Blocking => 0, Timeout => 1);
+       last if $fh;
+       if ($! != EINTR && $! != EAGAIN) {
+           die "unable to connect to qmeventd socket (vmid: $vmid) - $!\n";
+       }
+       if ($count > 4) {
+           die "unable to connect to qmeventd socket (vmid: $vmid) - timeout "
+             . "after $count retries\n";
+       }
+       usleep(25000);
+    }
+
+    # send handshake to mark VM as backing up
+    print $fh to_json({vzdump => {vmid => "$vmid"}});
+
+    # return handle to be closed later when inhibit is no longer required
+    return $fh;
+}
+
 # bash completion helper
 
 sub complete_backup_archives {
@@ -7373,4 +7705,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;