]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
live-restore: fail early if target storage doesn't exist
[qemu-server.git] / PVE / QemuServer.pm
index 719ed055015a685d848cd0372d2fa17be2759792..3126e14ea8f26e67f1c64417a3ae51647ee36862 100644 (file)
@@ -30,6 +30,7 @@ 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);
@@ -47,7 +48,7 @@ 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::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom drive_is_read_only parse_drive print_drive);
 use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::Monitor qw(mon_cmd);
@@ -314,11 +315,13 @@ my $confdesc = {
     cpuunits => {
        optional => 1,
        type => 'integer',
-        description => "CPU weight for a VM.",
-       verbose_description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.",
+        description => "CPU weight for a VM, will be clamped to [1, 10000] in cgroup v2.",
+       verbose_description => "CPU weight for a VM. Argument is used in the kernel fair scheduler."
+           ." The larger the number is, the more CPU time this VM gets. Number is relative to"
+           ." weights of all the other running VMs.",
        minimum => 2,
        maximum => 262144,
-       default => 1024,
+       default => 'cgroup v1: 1024, cgroup v2: 100',
     },
     memory => {
        optional => 1,
@@ -364,7 +367,9 @@ my $confdesc = {
     description => {
        optional => 1,
        type => 'string',
-       description => "Description for the VM. Only used on the configuration web interface. This is saved as comment inside the configuration file.",
+       description => "Description for the VM. Shown in the web-interface VM's summary."
+           ." This is saved as comment inside the configuration file.",
+       maxLength => 1024 * 8,
     },
     ostype => {
        optional => 1,
@@ -827,9 +832,22 @@ for (my $i = 0; $i < $MAX_NUMA; $i++)  {
     $confdesc->{"numa$i"} = $numadesc;
 }
 
-my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000',  'pcnet',  'virtio',
-                     'ne2k_isa', 'i82551', 'i82557b', 'i82559er', 'vmxnet3',
-                     'e1000-82540em', 'e1000-82544gc', 'e1000-82545em'];
+my $nic_model_list = [
+    'e1000',
+    'e1000-82540em',
+    'e1000-82544gc',
+    'e1000-82545em',
+    'e1000e',
+    'i82551',
+    'i82557b',
+    'i82559er',
+    'ne2k_isa',
+    'ne2k_pci',
+    'pcnet',
+    'rtl8139',
+    'virtio',
+    'vmxnet3',
+];
 my $nic_model_list_txt = join(' ', sort @$nic_model_list);
 
 my $net_fmt_bridge_descr = <<__EOD__;
@@ -1450,7 +1468,7 @@ sub print_drivedevice_full {
            }
        }
 
-       if (!$conf->{scsihw} || ($conf->{scsihw} =~ m/^lsi/)){
+       if (!$conf->{scsihw} || $conf->{scsihw} =~ m/^lsi/ || $conf->{scsihw} eq 'pvscsi') {
            $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,scsi-id=$unit";
        } else {
            $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,channel=0,scsi-id=0"
@@ -1520,21 +1538,22 @@ sub get_initiator_name {
 }
 
 sub print_drive_commandline_full {
-    my ($storecfg, $vmid, $drive, $pbs_name) = @_;
+    my ($storecfg, $vmid, $drive, $pbs_name, $io_uring) = @_;
 
     my $path;
     my $volid = $drive->{file};
     my $format = $drive->{format};
     my $drive_id = get_drive_id($drive);
 
+    my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+    my $scfg = $storeid ? PVE::Storage::storage_config($storecfg, $storeid) : undef;
+
     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);
        } else {
            $path = $volid;
@@ -1591,17 +1610,29 @@ sub print_drive_commandline_full {
 
     if (my $cache = $drive->{cache}) {
        $cache_direct = $cache =~ /^(?:off|none|directsync)$/;
-    } elsif (!drive_is_cdrom($drive)) {
+    } elsif (!drive_is_cdrom($drive) && !($scfg && $scfg->{type} eq 'btrfs' && !$scfg->{nocow})) {
        $opts .= ",cache=none";
        $cache_direct = 1;
     }
 
-    # aio native works only with O_DIRECT
+    # io_uring with cache mode writeback or writethrough on krbd will hang...
+    my $rbd_no_io_uring = $scfg && $scfg->{type} eq 'rbd' && $scfg->{krbd} && !$cache_direct;
+
+    # io_uring with cache mode writeback or writethrough on LVM will hang, without cache only
+    # sometimes, just plain disable...
+    my $lvm_no_io_uring = $scfg && $scfg->{type} eq 'lvm';
+
     if (!$drive->{aio}) {
-       if($cache_direct) {
-           $opts .= ",aio=native";
+       if ($io_uring && !$rbd_no_io_uring && !$lvm_no_io_uring) {
+           # io_uring supports all cache modes
+           $opts .= ",aio=io_uring";
        } else {
-           $opts .= ",aio=threads";
+           # aio native works only with O_DIRECT
+           if($cache_direct) {
+               $opts .= ",aio=native";
+           } else {
+               $opts .= ",aio=threads";
+           }
        }
     }
 
@@ -1690,6 +1721,8 @@ sub print_netdevice_full {
            $romfile = 'pxe-virtio.rom';
        } elsif ($device eq 'e1000') {
            $romfile = 'pxe-e1000.rom';
+       } elsif ($device eq 'e1000e') {
+           $romfile = 'pxe-e1000e.rom';
        } elsif ($device eq 'ne2k') {
            $romfile = 'pxe-ne2k_pci.rom';
        } elsif ($device eq 'pcnet') {
@@ -2126,7 +2159,7 @@ sub destroy_vm {
 
     if ($conf->{template}) {
        # check if any base image is still used by a linked clone
-       PVE::QemuConfig->foreach_volume($conf, sub {
+       PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, sub {
                my ($ds, $drive) = @_;
                return if drive_is_cdrom($drive);
 
@@ -2139,8 +2172,7 @@ sub destroy_vm {
        });
     }
 
-    # only remove disks owned by this VM (referenced in the config)
-    PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, sub {
+    my $remove_owned_drive = sub {
        my ($ds, $drive) = @_;
        return if drive_is_cdrom($drive, 1);
 
@@ -2152,10 +2184,24 @@ sub destroy_vm {
 
        eval { PVE::Storage::vdisk_free($storecfg, $volid) };
        warn "Could not remove disk '$volid', check manually: $@" if $@;
-    });
+    };
+
+    # only remove disks owned by this VM (referenced in the config)
+    my $include_opts = {
+       include_unused => 1,
+       extra_keys => ['vmstate'],
+    };
+    PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $remove_owned_drive);
+
+    for my $snap (values %{$conf->{snapshots}}) {
+       next if !defined($snap->{vmstate});
+       my $drive = PVE::QemuConfig->parse_volume('vmstate', $snap->{vmstate}, 1);
+       next if !defined($drive);
+       $remove_owned_drive->('vmstate', $drive);
+    }
 
     if ($purge_unreferenced) { # also remove unreferenced disk
-       my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
+       my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid, undef, 'images');
        PVE::Storage::foreach_volid($vmdisks, sub {
            my ($volid, $sid, $volname, $d) = @_;
            eval { PVE::Storage::vdisk_free($storecfg, $volid) };
@@ -2442,8 +2488,13 @@ sub check_storage_availability {
        return if !$sid;
 
        # check if storage is available on both nodes
-       my $scfg = PVE::Storage::storage_check_node($storecfg, $sid);
-       PVE::Storage::storage_check_node($storecfg, $sid, $node);
+       my $scfg = PVE::Storage::storage_check_enabled($storecfg, $sid);
+       PVE::Storage::storage_check_enabled($storecfg, $sid, $node);
+
+       my ($vtype) = PVE::Storage::parse_volname($storecfg, $volid);
+
+       die "$volid: content type '$vtype' is not available on storage '$sid'\n"
+           if !$scfg->{content}->{$vtype};
    });
 }
 
@@ -2637,8 +2688,8 @@ sub vmstatus {
 
        my $conf = PVE::QemuConfig->load_config($vmid);
 
-       my $d = { vmid => $vmid };
-       $d->{pid} = $list->{$vmid}->{pid};
+       my $d = { vmid => int($vmid) };
+       $d->{pid} = int($list->{$vmid}->{pid}) if $list->{$vmid}->{pid};
 
        # fixme: better status?
        $d->{status} = $list->{$vmid}->{pid} ? 'running' : 'stopped';
@@ -2677,7 +2728,7 @@ sub vmstatus {
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
 
-        $d->{template} = PVE::QemuConfig->is_template($conf);
+        $d->{template} = 1 if PVE::QemuConfig->is_template($conf);
 
        $d->{serial} = 1 if conf_has_serial($conf);
        $d->{lock} = $conf->{lock} if $conf->{lock};
@@ -2697,8 +2748,8 @@ sub vmstatus {
        $d->{netin} += $netdev->{$dev}->{transmit};
 
        if ($full) {
-           $d->{nics}->{$dev}->{netout} = $netdev->{$dev}->{receive};
-           $d->{nics}->{$dev}->{netin} = $netdev->{$dev}->{transmit};
+           $d->{nics}->{$dev}->{netout} = int($netdev->{$dev}->{receive});
+           $d->{nics}->{$dev}->{netin} = int($netdev->{$dev}->{transmit});
        }
 
     }
@@ -2985,10 +3036,15 @@ sub get_vm_machine {
        }
     }
 
-    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;
@@ -3063,7 +3119,7 @@ sub query_supported_cpu_flags {
            $qemu_cmd,
            '-machine', $default_machine,
            '-display', 'none',
-           '-chardev', "socket,id=qmp,path=/var/run/qemu-server/$fakevmid.qmp,server,nowait",
+           '-chardev', "socket,id=qmp,path=/var/run/qemu-server/$fakevmid.qmp,server=on,wait=off",
            '-mon', 'chardev=qmp,mode=control',
            '-pidfile', $pidfile,
            '-S', '-daemonize'
@@ -3135,6 +3191,10 @@ sub query_understood_cpu_flags {
     return \@flags;
 }
 
+my sub get_cpuunits {
+    my ($conf) = @_;
+    return $conf->{cpuunits} // (PVE::CGroup::cgroup_mode() == 2 ? 100 : 1024);
+}
 sub config_to_command {
     my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu,
         $pbs_backing) = @_;
@@ -3204,8 +3264,7 @@ sub config_to_command {
     my $use_old_bios_files = undef;
     ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
-    my $cpuunits = defined($conf->{cpuunits}) ?
-            $conf->{cpuunits} : $defaults->{cpuunits};
+    my $cpuunits = get_cpuunits($conf);
 
     push @$cmd, $kvm_binary;
 
@@ -3220,7 +3279,7 @@ sub config_to_command {
     my $use_virtio = 0;
 
     my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket($vmid);
-    push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
+    push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server=on,wait=off";
     push @$cmd, '-mon', "chardev=qmp,mode=control";
 
     if (min_version($machine_version, 2, 12)) {
@@ -3260,6 +3319,7 @@ sub config_to_command {
        die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code;
 
        my ($path, $format);
+       my $read_only_str = '';
        if (my $efidisk = $conf->{efidisk0}) {
            my $d = parse_drive('efidisk0', $efidisk);
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
@@ -3275,6 +3335,8 @@ sub config_to_command {
                die "efidisk format must be specified\n"
                    if !defined($format);
            }
+
+           $read_only_str = ',readonly=on' if drive_is_read_only($conf, $d);
        } else {
            warn "no efidisk configured! Using temporary efivars disk.\n";
            $path = "/tmp/$vmid-ovmf.fd";
@@ -3288,8 +3350,15 @@ sub config_to_command {
            $size_str = ",size=" . (-s $ovmf_vars);
        }
 
-       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$ovmf_code";
-       push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0$size_str,file=$path";
+       # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329
+       my $cache = "";
+       if ($path =~ m/^rbd:/) {
+               $cache = ',cache=writeback';
+               $path .= ':rbd_cache_policy=writeback'; # avoid write-around, we *need* to cache writes too
+       }
+
+       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly=on,file=$ovmf_code";
+       push @$cmd, '-drive', "if=pflash,unit=1$cache,format=$format,id=drive-efidisk0$size_str,file=${path}${read_only_str}";
     }
 
     # load q35 config
@@ -3360,7 +3429,7 @@ sub config_to_command {
        if (my $path = $conf->{"serial$i"}) {
            if ($path eq 'socket') {
                my $socket = "/var/run/qemu-server/${vmid}.serial$i";
-               push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server,nowait";
+               push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server=on,wait=off";
                # On aarch64, serial0 is the UART device. Qemu only allows
                # connecting UART devices via the '-serial' command line, as
                # the device has a fixed slot on the hardware...
@@ -3432,7 +3501,7 @@ sub config_to_command {
        push @$devices, '-device', print_vga_device(
            $conf, $vga, $arch, $machine_version, $machine_type, undef, $qxlnum, $bridges);
        my $socket = PVE::QemuServer::Helpers::vnc_socket($vmid);
-       push @$cmd,  '-vnc', "unix:$socket,password";
+       push @$cmd,  '-vnc', "unix:$socket,password=on";
     } else {
        push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
        push @$cmd, '-nographic';
@@ -3480,7 +3549,7 @@ sub config_to_command {
 
     if ($guest_agent->{enabled}) {
        my $qgasocket = PVE::QemuServer::Helpers::qmp_socket($vmid, 1);
-       push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
+       push @$devices, '-chardev', "socket,path=$qgasocket,server=on,wait=off,id=qga0";
 
        if (!$guest_agent->{type} || $guest_agent->{type} eq 'virtio') {
            my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
@@ -3583,6 +3652,7 @@ sub config_to_command {
        my ($ds, $drive) = @_;
 
        if (PVE::Storage::parse_volume_id($drive->{file}, 1)) {
+           check_volume_storage_type($storecfg, $drive->{file});
            push @$vollist, $drive->{file};
        }
 
@@ -3640,8 +3710,11 @@ sub config_to_command {
            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);
+       my $drive_cmd = print_drive_commandline_full(
+           $storecfg, $vmid, $drive, $pbs_name, min_version($kvmver, 6, 0));
+
+       # extra protection for templates, but SATA and IDE don't support it..
+       $drive_cmd .= ',readonly=on' if drive_is_read_only($conf, $drive);
 
        push @$devices, '-drive',$drive_cmd;
        push @$devices, '-device', print_drivedevice_full(
@@ -3741,6 +3814,11 @@ sub config_to_command {
        print "activating and using '$vmstate' as vmstate\n";
     }
 
+    if (PVE::QemuConfig->is_template($conf)) {
+       # needed to workaround base volumes being read-only
+       push @$cmd, '-snapshot';
+    }
+
     # add custom args
     if ($conf->{args}) {
        my $aa = PVE::Tools::split_args($conf->{args});
@@ -4048,7 +4126,9 @@ sub qemu_objectdel {
 sub qemu_driveadd {
     my ($storecfg, $vmid, $device) = @_;
 
-    my $drive = print_drive_commandline_full($storecfg, $vmid, $device);
+    my $kvmver = get_running_qemu_version($vmid);
+    my $io_uring = min_version($kvmver, 6, 0);
+    my $drive = print_drive_commandline_full($storecfg, $vmid, $device, undef, $io_uring);
     $drive =~ s/\\/\\\\/g;
     my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_add auto \"$drive\"");
 
@@ -4452,6 +4532,8 @@ sub foreach_volid {
 
        $volhash->{$volid}->{is_unused} //= 0;
        $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/;
+
+       $volhash->{$volid}->{drivename} = $key if is_valid_drivename($key);
     };
 
     my $include_opts = {
@@ -4563,7 +4645,7 @@ 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') {
-               $cgroup->change_cpu_shares(undef, $defaults->{cpuunits});
+               $cgroup->change_cpu_shares(undef, 1024);
            } elsif ($opt eq 'cpulimit') {
                $cgroup->change_cpu_quota(-1, 100000);
            } else {
@@ -4657,7 +4739,7 @@ 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') {
-               $cgroup->change_cpu_shares($conf->{pending}->{$opt}, $defaults->{cpuunits});
+               $cgroup->change_cpu_shares($conf->{pending}->{$opt}, 1024);
            } elsif ($opt eq 'cpulimit') {
                my $cpulimit = $conf->{pending}->{$opt} == 0 ? -1 : int($conf->{pending}->{$opt} * 100000);
                $cgroup->change_cpu_quota($cpulimit, 100000);
@@ -5224,8 +5306,7 @@ sub vm_start_nolock {
     # timeout should be more than enough here...
     PVE::Systemd::wait_for_unit_removed("$vmid.scope", 5);
 
-    my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
-                                             : $defaults->{cpuunits};
+    my $cpuunits = get_cpuunits($conf);
 
     my $start_timeout = $params->{timeout} // config_aware_timeout($conf, $resume);
     my %run_params = (
@@ -5243,10 +5324,13 @@ sub vm_start_nolock {
 
     my %properties = (
        Slice => 'qemu.slice',
-       KillMode => 'none'
+       KillMode => 'process',
+       SendSIGKILL => 0,
+       TimeoutStopUSec => ULONG_MAX, # infinity
     );
 
     if (PVE::CGroup::cgroup_mode() == 2) {
+       $cpuunits = 10000 if $cpuunits >= 10000; # else we get an error
        $properties{CPUWeight} = $cpuunits;
     } else {
        $properties{CPUShares} = $cpuunits;
@@ -5965,8 +6049,8 @@ my $restore_allocate_devices = sub {
     return $map;
 };
 
-my $restore_update_config_line = sub {
-    my ($cookie, $vmid, $map, $line, $unique) = @_;
+sub restore_update_config_line {
+    my ($cookie, $map, $line, $unique) = @_;
 
     return '' if $line =~ m/^\#qmdump\#/;
     return '' if $line =~ m/^\#vzdump\#/;
@@ -6032,7 +6116,7 @@ my $restore_update_config_line = sub {
     }
 
     return $res;
-};
+}
 
 my $restore_deactivate_volumes = sub {
     my ($storecfg, $devinfo) = @_;
@@ -6067,7 +6151,7 @@ my $restore_destroy_volumes = sub {
 sub scan_volids {
     my ($cfg, $vmid) = @_;
 
-    my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid);
+    my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid, undef, 'images');
 
     my $volid_hash = {};
     foreach my $storeid (keys %$info) {
@@ -6160,12 +6244,6 @@ sub rescan {
 
     my $cfg = PVE::Storage::config();
 
-    # FIXME: Remove once our RBD plugin can handle CT and VM on a single storage
-    # see: https://pve.proxmox.com/pipermail/pve-devel/2018-July/032900.html
-    foreach my $stor (keys %{$cfg->{ids}}) {
-       delete($cfg->{ids}->{$stor}) if ! $cfg->{ids}->{$stor}->{content}->{images};
-    }
-
     print "rescan volumes...\n";
     my $volid_hash = scan_volids($cfg, $vmid);
 
@@ -6315,44 +6393,44 @@ sub restore_proxmox_backup_archive {
        # allocate volumes
        my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid);
 
-       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};
+       foreach my $virtdev (sort keys %$virtdev_hash) {
+           my $d = $virtdev_hash->{$virtdev};
+           next if $d->{is_cloudinit}; # no need to restore cloudinit
 
-               my $path = PVE::Storage::path($storecfg, $volid);
+           # this fails if storage is unavailable
+           my $volid = $d->{volid};
+           my $path = PVE::Storage::path($storecfg, $volid);
 
-               my $pbs_restore_cmd = [
-                   '/usr/bin/pbs-restore',
-                   '--repository', $repo,
-                   $pbs_backup_name,
-                   "$d->{devname}.img.fidx",
-                   $path,
-                   '--verbose',
-                   ];
+           # for live-restore we only want to preload the efidisk
+           next if $options->{live} && $virtdev ne 'efidisk0';
 
-               push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
-               push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
+           my $pbs_restore_cmd = [
+               '/usr/bin/pbs-restore',
+               '--repository', $repo,
+               $pbs_backup_name,
+               "$d->{devname}.img.fidx",
+               $path,
+               '--verbose',
+               ];
 
-               if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
-                   push @$pbs_restore_cmd, '--skip-zero';
-               }
+           push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+           push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
 
-               my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);
-               print "restore proxmox backup image: $dbg_cmdstring\n";
-               run_command($pbs_restore_cmd);
+           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);
        }
 
        $fh->seek(0, 0) || die "seek failed - $!\n";
 
        my $cookie = { netcount => 0 };
        while (defined(my $line = <$fh>)) {
-           $new_conf_raw .= $restore_update_config_line->(
+           $new_conf_raw .= restore_update_config_line(
                $cookie,
-               $vmid,
                $map,
                $line,
                $options->{unique},
@@ -6374,6 +6452,11 @@ sub restore_proxmox_backup_archive {
        die $err;
     }
 
+    if ($options->{live}) {
+       # keep lock during live-restore
+       $new_conf_raw .= "\nlock: create";
+    }
+
     PVE::Tools::file_set_contents($conffile, $new_conf_raw);
 
     PVE::Cluster::cfs_update(); # make sure we read new file
@@ -6384,68 +6467,54 @@ sub restore_proxmox_backup_archive {
     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 "interrupted by signal\n"; };
+       # 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);
+       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);
-       };
+       delete $devinfo->{'drive-efidisk0'}; # this special drive is already restored before start
+       pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $repo, $keyfile, $pbs_backup_name);
 
-       $err = $@;
-       if ($err) {
-           warn "Detroying live-restore VM, all temporary data will be lost!\n";
-           $restore_deactivate_volumes->($storecfg, $devinfo);
-           $restore_destroy_volumes->($storecfg, $devinfo);
-           unlink $conffile;
-           die $err;
-       }
+       PVE::QemuConfig->remove_lock($vmid, "create");
     }
 }
 
 sub pbs_live_restore {
     my ($vmid, $conf, $storecfg, $restored_disks, $repo, $keyfile, $snap) = @_;
 
-    print "Starting VM for live-restore\n";
+    print "starting VM for live-restore\n";
+    print "repository: '$repo', snapshot: '$snap'\n";
 
     my $pbs_backing = {};
     for my $ds (keys %$restored_disks) {
        $ds =~ m/^drive-(.*)$/;
-       $pbs_backing->{$1} = {
+       my $confname = $1;
+       $pbs_backing->{$confname} = {
            repository => $repo,
            snapshot => $snap,
            archive => "$ds.img.fidx",
        };
-       $pbs_backing->{$1}->{keyfile} = $keyfile if -e $keyfile;
+       $pbs_backing->{$confname}->{keyfile} = $keyfile if -e $keyfile;
+
+       my $drive = parse_drive($confname, $conf->{$confname});
+       print "restoring '$ds' to '$drive->{file}'\n";
     }
 
+    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)) {
-           my $cmd = ['ha-manager', 'set',  "vm:$vmid", '--state', 'started'];
-           PVE::Tools::run_command($cmd);
-       }
-
-       # 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
-       PVE::QemuServer::vm_start_nolock(
-           $storecfg,
-           $vmid,
-           $conf,
-           {
-               paused => 1,
-               'pbs-backing' => $pbs_backing,
-           },
-           {},
-       );
+           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);
 
@@ -6466,7 +6535,9 @@ sub pbs_live_restore {
        mon_cmd($vmid, 'cont');
        qemu_drive_mirror_monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
 
-       # all jobs finished, remove blockdevs now to disconnect from PBS
+       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");
        }
@@ -6626,9 +6697,8 @@ sub restore_vma_archive {
 
        my $cookie = { netcount => 0 };
        while (defined(my $line = <$fh>)) {
-           $new_conf_raw .= $restore_update_config_line->(
+           $new_conf_raw .= restore_update_config_line(
                $cookie,
-               $vmid,
                $map,
                $line,
                $opts->{unique},
@@ -6783,9 +6853,8 @@ sub restore_tar_archive {
 
        my $cookie = { netcount => 0 };
        while (defined (my $line = <$srcfd>)) {
-           $new_conf_raw .= $restore_update_config_line->(
+           $new_conf_raw .= restore_update_config_line(
                $cookie,
-               $vmid,
                $map,
                $line,
                $opts->{unique},
@@ -6968,9 +7037,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%)\n";
        }
 
     };
@@ -7056,10 +7126,12 @@ sub qemu_drive_mirror_monitor {
     eval {
        my $err_complete = 0;
 
+       my $starttime = time ();
        while (1) {
            die "block job ('$op') timed out\n" if $err_complete > 300;
 
            my $stats = mon_cmd($vmid, "query-block-jobs");
+           my $ctime = time();
 
            my $running_jobs = {};
            for my $stat (@$stats) {
@@ -7075,7 +7147,7 @@ sub qemu_drive_mirror_monitor {
                my $vanished = !defined($job);
                my $complete = defined($jobs->{$job_id}->{complete}) && $vanished;
                if($complete || ($vanished && $completion eq 'auto')) {
-                   print "$job_id: finished\n";
+                   print "$job_id: $op-job finished\n";
                    delete $jobs->{$job_id};
                    next;
                }
@@ -7089,7 +7161,24 @@ sub qemu_drive_mirror_monitor {
                    my $remaining = $total - $transferred;
                    my $percent = sprintf "%.2f", ($transferred * 100 / $total);
 
-                   print "$job_id: 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 $job->{ready};
@@ -7141,7 +7230,7 @@ sub qemu_drive_mirror_monitor {
                        }
                        eval { mon_cmd($vmid, $op, device => $job_id) };
                        if ($@ =~ m/cannot be completed/) {
-                           print "$job_id: Block job cannot be completed, try again.\n";
+                           print "$job_id: block job cannot be completed, trying again.\n";
                            $err_complete++;
                        }else {
                            print "$job_id: Completed successfully.\n";
@@ -7157,9 +7246,8 @@ sub qemu_drive_mirror_monitor {
 
     if ($err) {
        eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
-       die "block job ('$op') error: $err";
+       die "block job ($op) error: $err";
     }
-
 }
 
 sub qemu_blockjobs_cancel {
@@ -7699,4 +7787,17 @@ sub vm_is_paused {
     return $qmpstatus && $qmpstatus->{status} eq "paused";
 }
 
+sub check_volume_storage_type {
+    my ($storecfg, $vol) = @_;
+
+    my ($storeid, $volname) = PVE::Storage::parse_volume_id($vol);
+    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+    my ($vtype) = PVE::Storage::parse_volname($storecfg, $vol);
+
+    die "storage '$storeid' does not support content-type '$vtype'\n"
+       if !$scfg->{content}->{$vtype};
+
+    return 1;
+}
+
 1;