]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix #4324: USB: use qemu-xhci for machine versions >= 7.1
[qemu-server.git] / PVE / QemuServer.pm
index b3964bc6f07d05ea4c8714c23a381664f4ed2a9b..5773e641c319384f00b1d18dc63d4a4b52cca2b8 100644 (file)
@@ -28,6 +28,7 @@ use UUID;
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
 use PVE::CGroup;
+use PVE::CpuSet;
 use PVE::DataCenterConfig;
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::Format qw(render_duration render_bytes);
@@ -36,6 +37,7 @@ use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option parse_property_string);
 use PVE::ProcFSTools;
 use PVE::PBSClient;
+use PVE::RESTEnvironment qw(log_warn);
 use PVE::RPCEnvironment;
 use PVE::Storage;
 use PVE::SysFSTools;
@@ -44,7 +46,7 @@ use PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_for
 
 use PVE::QMPClient;
 use PVE::QemuConfig;
-use PVE::QemuServer::Helpers qw(min_version config_aware_timeout);
+use PVE::QemuServer::Helpers qw(min_version config_aware_timeout windows_version);
 use PVE::QemuServer::Cloudinit;
 use PVE::QemuServer::CGroup;
 use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options);
@@ -119,13 +121,6 @@ PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
        optional => 1,
 });
 
-PVE::JSONSchema::register_standard_option('pve-targetstorage', {
-    description => "Mapping from source to target storages. Providing only a single storage ID maps all source storages to that storage. Providing the special value '1' will map each source storage to itself.",
-    type => 'string',
-    format => 'storage-pair-list',
-    optional => 1,
-});
-
 #no warnings 'redefine';
 
 my $nodename_cache;
@@ -303,7 +298,7 @@ my $confdesc = {
        optional => 1,
        type => 'string', format => 'pve-hotplug-features',
        description => "Selectively enable hotplug features. This is a comma separated list of"
-           ." hotplug features: 'network', 'disk', 'cpu', 'memory' and 'usb'. Use '0' to disable"
+           ." hotplug features: 'network', 'disk', 'cpu', 'memory', 'usb' and 'cloudinit'. Use '0' to disable"
            ." hotplug completely. Using '1' as value is an alias for the default `network,disk,usb`.",
         default => 'network,disk,usb',
     },
@@ -713,6 +708,11 @@ EODESCR
        description => "Some (read-only) meta-information about this guest.",
        optional => 1,
     },
+    affinity => {
+       type => 'string', format => 'pve-cpuset',
+       description => "List of host cores used to execute guest processes.",
+       optional => 1,
+    },
 };
 
 my $cicustom_fmt = {
@@ -783,16 +783,16 @@ my $confdesc_cloudinit = {
     searchdomain => {
        optional => 1,
        type => 'string',
-       description => "cloud-init: Sets DNS search domains for a container. Create will'
+       description => 'cloud-init: Sets DNS search domains for a container. Create will'
            .' automatically use the setting from the host if neither searchdomain nor nameserver'
-           .' are set.",
+           .' are set.',
     },
     nameserver => {
        optional => 1,
        type => 'string', format => 'address-list',
-       description => "cloud-init: Sets DNS server IP address for a container. Create will'
+       description => 'cloud-init: Sets DNS server IP address for a container. Create will'
            .' automatically use the setting from the host if neither searchdomain nor nameserver'
-           .' are set.",
+           .' are set.',
     },
     sshkeys => {
        optional => 1,
@@ -1032,6 +1032,20 @@ foreach my $key (keys %$confdesc_cloudinit) {
     $confdesc->{$key} = $confdesc_cloudinit->{$key};
 }
 
+PVE::JSONSchema::register_format('pve-cpuset', \&pve_verify_cpuset);
+sub pve_verify_cpuset {
+    my ($set_text, $noerr) = @_;
+
+    my ($count, $members) = eval { PVE::CpuSet::parse_cpuset($set_text) };
+
+    if ($@) {
+       return if $noerr;
+       die "unable to parse cpuset option\n";
+    }
+
+    return PVE::CpuSet->new($members)->short_string();
+}
+
 PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path);
 sub verify_volume_id_or_qm_path {
     my ($volid, $noerr) = @_;
@@ -1078,7 +1092,9 @@ EODESCR
     usb3 => {
        optional => 1,
        type => 'boolean',
-       description => "Specifies whether if given host option is a USB3 device or port.",
+       description => "Specifies whether if given host option is a USB3 device or port."
+           ." For modern guests (machine version >= 7.1 and ostype l26 and windows > 7), this flag"
+           ." is irrelevant (all devices are plugged into a xhci controller).",
         default => 0,
     },
 };
@@ -1356,7 +1372,7 @@ sub parse_hotplug_features {
     $data = $confdesc->{hotplug}->{default} if $data eq '1';
 
     foreach my $feature (PVE::Tools::split_list($data)) {
-       if ($feature =~ m/^(network|disk|cpu|memory|usb)$/) {
+       if ($feature =~ m/^(network|disk|cpu|memory|usb|cloudinit)$/) {
            $res->{$1} = 1;
        } else {
            die "invalid hotplug feature '$feature'\n";
@@ -1443,7 +1459,7 @@ sub print_tabletdevice_full {
 
     # we use uhci for old VMs because tablet driver was buggy in older qemu
     my $usbbus;
-    if (PVE::QemuServer::Machine::machine_type_is_q35($conf) || $arch eq 'aarch64') {
+    if ($q35 || $arch eq 'aarch64') {
        $usbbus = 'ehci';
     } else {
        $usbbus = 'uhci';
@@ -1664,8 +1680,12 @@ sub print_drive_commandline_full {
     # sometimes, just plain disable...
     my $lvm_no_io_uring = $scfg && $scfg->{type} eq 'lvm';
 
+    # io_uring causes problems when used with CIFS since kernel 5.15
+    # Some discussion: https://www.spinics.net/lists/linux-cifs/msg26734.html
+    my $cifs_no_io_uring = $scfg && $scfg->{type} eq 'cifs';
+
     if (!$drive->{aio}) {
-       if ($io_uring && !$rbd_no_io_uring && !$lvm_no_io_uring) {
+       if ($io_uring && !$rbd_no_io_uring && !$lvm_no_io_uring && !$cifs_no_io_uring) {
            # io_uring supports all cache modes
            $opts .= ",aio=io_uring";
        } else {
@@ -1717,6 +1737,7 @@ 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 .= ",namespace=$pbs_conf->{namespace}" if $pbs_conf->{namespace};
     $blockdev .= ",snapshot=$pbs_conf->{snapshot}";
     $blockdev .= ",archive=$pbs_conf->{archive}";
     $blockdev .= ",keyfile=$pbs_conf->{keyfile}" if $pbs_conf->{keyfile};
@@ -2003,6 +2024,7 @@ sub vmconfig_register_unused_drive {
     if (drive_is_cloudinit($drive)) {
        eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) };
        warn $@ if $@;
+       delete $conf->{cloudinit};
     } elsif (!drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
        if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
@@ -2373,6 +2395,7 @@ sub parse_vm_config {
        digest => Digest::SHA::sha1_hex($raw),
        snapshots => {},
        pending => {},
+       cloudinit => {},
     };
 
     my $handle_error = sub {
@@ -2407,6 +2430,11 @@ sub parse_vm_config {
            $descr = undef;
            $conf = $res->{$section} = {};
            next;
+       } elsif ($line =~ m/^\[special:cloudinit\]\s*$/i) {
+           $section = 'cloudinit';
+           $descr = undef;
+           $conf = $res->{$section} = {};
+           next;
 
        } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
            $section = $1;
@@ -2504,7 +2532,7 @@ sub write_vm_config {
 
        foreach my $key (keys %$cref) {
            next if $key eq 'digest' || $key eq 'description' || $key eq 'snapshots' ||
-               $key eq 'snapstate' || $key eq 'pending';
+               $key eq 'snapstate' || $key eq 'pending' || $key eq 'cloudinit';
            my $value = $cref->{$key};
            if ($key eq 'delete') {
                die "propertry 'delete' is only allowed in [PENDING]\n"
@@ -2528,6 +2556,8 @@ sub write_vm_config {
 
     &$cleanup_config($conf->{pending}, 1);
 
+    &$cleanup_config($conf->{cloudinit});
+
     foreach my $snapname (keys %{$conf->{snapshots}}) {
        die "internal error: snapshot name '$snapname' is forbidden" if lc($snapname) eq 'pending';
        &$cleanup_config($conf->{snapshots}->{$snapname}, undef, $snapname);
@@ -2558,7 +2588,7 @@ sub write_vm_config {
        }
 
        foreach my $key (sort keys %$conf) {
-           next if $key =~ /^(digest|description|pending|snapshots)$/;
+           next if $key =~ /^(digest|description|pending|cloudinit|snapshots)$/;
            $raw .= "$key: $conf->{$key}\n";
        }
        return $raw;
@@ -2571,6 +2601,11 @@ sub write_vm_config {
        $raw .= &$generate_raw_config($conf->{pending}, 1);
     }
 
+    if (scalar(keys %{$conf->{cloudinit}})){
+       $raw .= "\n[special:cloudinit]\n";
+       $raw .= &$generate_raw_config($conf->{cloudinit});
+    }
+
     foreach my $snapname (sort keys %{$conf->{snapshots}}) {
        $raw .= "\n[$snapname]\n";
        $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
@@ -3444,21 +3479,6 @@ sub query_understood_cpu_flags {
     return \@flags;
 }
 
-my sub get_cpuunits {
-    my ($conf) = @_;
-    my $is_cgroupv2 = PVE::CGroup::cgroup_mode() == 2;
-
-    my $cpuunits = $conf->{cpuunits};
-    return $is_cgroupv2 ? 100 : 1024 if !defined($cpuunits);
-
-    if ($is_cgroupv2) {
-       $cpuunits = 10000 if $cpuunits >= 10000; # v1 can be higher, so clamp v2 there
-    } else {
-       $cpuunits = 2 if $cpuunits < 2; # v2 can be lower, so clamp v1 there
-    }
-    return $cpuunits;
-}
-
 # Since commit 277d33454f77ec1d1e0bc04e37621e4dd2424b67 in pve-qemu, smm is not off by default
 # anymore. But smm=off seems to be required when using SeaBIOS and serial display.
 my sub should_disable_smm {
@@ -3534,6 +3554,13 @@ sub config_to_command {
     my $use_old_bios_files = undef;
     ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
+    if ($conf->{affinity}) {
+       push @$cmd, "/usr/bin/taskset";
+       push @$cmd, "--cpu-list";
+       push @$cmd, "--all-tasks";
+       push @$cmd, $conf->{affinity};
+    }
+
     push @$cmd, $kvm_binary;
 
     push @$cmd, '-id', $vmid;
@@ -3610,7 +3637,7 @@ sub config_to_command {
 
            $read_only_str = ',readonly=on' if drive_is_read_only($conf, $d);
        } else {
-           warn "no efidisk configured! Using temporary efivars disk.\n";
+           log_warn("no efidisk configured! Using temporary efivars disk.");
            $path = "/tmp/$vmid-ovmf.fd";
            PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
            $format = 'raw';
@@ -3652,7 +3679,7 @@ sub config_to_command {
 
     # add usb controllers
     my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers(
-        $conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES);
+       $conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES, $machine_version);
     push @$devices, @usbcontrollers if @usbcontrollers;
     my $vga = parse_vga($conf->{vga});
 
@@ -3694,7 +3721,7 @@ sub config_to_command {
     $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, $bootorder);
+       $conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features, $bootorder, $machine_version);
     push @$devices, @usbdevices if @usbdevices;
 
     # serial devices
@@ -3961,7 +3988,9 @@ sub config_to_command {
                $iothread .= ",iothread=iothread-$controller_prefix$controller";
                push @$cmd, '-object', "iothread,id=iothread-$controller_prefix$controller";
            } elsif ($drive->{iothread}) {
-               warn "iothread is only valid with virtio disk or virtio-scsi-single controller, ignoring\n";
+               log_warn(
+                   "iothread is only valid with virtio disk or virtio-scsi-single controller, ignoring\n"
+               );
            }
 
            my $queues = '';
@@ -4818,6 +4847,10 @@ my $fast_plug_option = {
     'tags' => 1,
 };
 
+for my $opt (keys %$confdesc_cloudinit) {
+    $fast_plug_option->{$opt} = 1;
+};
+
 # hotplug changes in [PENDING]
 # $selection hash can be used to only apply specified options, for
 # example: { cores => 1 } (only apply changed 'cores')
@@ -4898,7 +4931,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, 1024);
+               $cgroup->change_cpu_shares(undef);
            } elsif ($opt eq 'cpulimit') {
                $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
            } else {
@@ -4913,31 +4946,6 @@ sub vmconfig_hotplug_pending {
        }
     }
 
-    my ($apply_pending_cloudinit, $apply_pending_cloudinit_done);
-    $apply_pending_cloudinit = sub {
-       return if $apply_pending_cloudinit_done; # once is enough
-       $apply_pending_cloudinit_done = 1; # once is enough
-
-       my ($key, $value) = @_;
-
-       my @cloudinit_opts = keys %$confdesc_cloudinit;
-       foreach my $opt (keys %{$conf->{pending}}) {
-           next if !grep { $_ eq $opt } @cloudinit_opts;
-           $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);
-    };
-
     foreach my $opt (keys %{$conf->{pending}}) {
        next if $selection && !$selection->{$opt};
        my $value = $conf->{pending}->{$opt};
@@ -4984,7 +4992,7 @@ sub vmconfig_hotplug_pending {
                # some changes can be done without hotplug
                my $drive = parse_drive($opt, $value);
                if (drive_is_cloudinit($drive)) {
-                   &$apply_pending_cloudinit($opt, $value);
+                   PVE::QemuServer::Cloudinit::generate_cloudinitconfig($conf, $vmid);
                }
                vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
                                     $vmid, $opt, $value, $arch, $machine_type);
@@ -4992,8 +5000,8 @@ 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') {
-               my $new_cpuunits = get_cpuunits({ $opt => $conf->{pending}->{$opt} }); # to clamp
-               $cgroup->change_cpu_shares($new_cpuunits, 1024);
+               my $new_cpuunits = PVE::CGroup::clamp_cpu_shares($conf->{pending}->{$opt}); #clamp
+               $cgroup->change_cpu_shares($new_cpuunits);
            } elsif ($opt eq 'cpulimit') {
                my $cpulimit = $conf->{pending}->{$opt} == 0 ? -1 : int($conf->{pending}->{$opt} * 100000);
                $cgroup->change_cpu_quota($cpulimit, 100000);
@@ -5010,8 +5018,16 @@ sub vmconfig_hotplug_pending {
            delete $conf->{pending}->{$opt};
        }
     }
-
     PVE::QemuConfig->write_config($vmid, $conf);
+
+    if($hotplug_features->{cloudinit}) {
+       my $pending = PVE::QemuServer::Cloudinit::get_pending_config($conf, $vmid);
+       my $regenerate = undef;
+       for my $item (@$pending) {
+           $regenerate = 1 if defined($item->{delete}) or defined($item->{pending});
+       }
+       PVE::QemuServer::vmconfig_update_cloudinit_drive($storecfg, $conf, $vmid) if $regenerate;
+    }
 }
 
 sub try_deallocate_drive {
@@ -5089,6 +5105,8 @@ sub vmconfig_apply_pending {
 
     PVE::QemuConfig->cleanup_pending($conf);
 
+    my $generate_cloudnit = undef;
+
     foreach my $opt (keys %{$conf->{pending}}) { # add/change
        next if $opt eq 'delete'; # just to be sure
        eval {
@@ -5099,12 +5117,19 @@ sub vmconfig_apply_pending {
        if (my $err = $@) {
            $add_apply_error->($opt, $err);
        } else {
+
+           if (is_valid_drivename($opt)) {
+               my $drive = parse_drive($opt, $conf->{pending}->{$opt});
+               $generate_cloudnit = 1 if drive_is_cloudinit($drive);
+           }
+
            $conf->{$opt} = delete $conf->{pending}->{$opt};
        }
     }
 
     # write all changes at once to avoid unnecessary i/o
     PVE::QemuConfig->write_config($vmid, $conf);
+    PVE::QemuServer::Cloudinit::generate_cloudinitconfig($conf, $vmid) if $generate_cloudnit;
 }
 
 sub vmconfig_update_net {
@@ -5291,6 +5316,34 @@ sub vmconfig_update_disk {
     vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive, $arch, $machine_type);
 }
 
+sub vmconfig_update_cloudinit_drive {
+    my ($storecfg, $conf, $vmid) = @_;
+
+    my $cloudinit_ds = undef;
+    my $cloudinit_drive = undef;
+
+    PVE::QemuConfig->foreach_volume($conf, sub {
+       my ($ds, $drive) = @_;
+       if (PVE::QemuServer::drive_is_cloudinit($drive)) {
+           $cloudinit_ds = $ds;
+           $cloudinit_drive = $drive;
+       }
+    });
+
+    return if !$cloudinit_drive;
+
+    PVE::QemuServer::Cloudinit::generate_cloudinitconfig($conf, $vmid);
+    my $running = PVE::QemuServer::check_running($vmid);
+
+    if ($running) {
+       my $path = PVE::Storage::path($storecfg, $cloudinit_drive->{file});
+       if ($path) {
+           mon_cmd($vmid, "eject", force => JSON::true, id => "$cloudinit_ds");
+           mon_cmd($vmid, "blockdev-change-medium", id => "$cloudinit_ds", filename => "$path");
+       }
+    }
+}
+
 # called in locked context by incoming migration
 sub vm_migrate_get_nbd_disks {
     my ($storecfg, $conf, $replicated_volumes) = @_;
@@ -5593,15 +5646,22 @@ sub vm_start_nolock {
     PVE::QemuServer::PCI::reserve_pci_usage($pci_id_list, $vmid, $start_timeout);
 
     eval {
+       my $uuid;
        for my $id (sort keys %$pci_devices) {
            my $d = $pci_devices->{$id};
            for my $dev ($d->{pciid}->@*) {
-               PVE::QemuServer::PCI::prepare_pci_device($vmid, $dev->{id}, $id, $d->{mdev});
+               my $info = PVE::QemuServer::PCI::prepare_pci_device($vmid, $dev->{id}, $id, $d->{mdev});
+
+               # nvidia grid needs the uuid of the mdev as qemu parameter
+               if ($d->{mdev} && !defined($uuid) && $info->{vendor} eq '10de') {
+                   $uuid = PVE::QemuServer::PCI::generate_mdev_uuid($vmid, $id);
+               }
            }
        }
+       push @$cmd, '-uuid', $uuid if defined($uuid);
     };
     if (my $err = $@) {
-       eval { PVE::QemuServer::PCI::remove_pci_reservation($pci_id_list) };
+       eval { cleanup_pci_devices($vmid, $conf) };
        warn $@ if $@;
        die $err;
     }
@@ -5615,7 +5675,7 @@ sub vm_start_nolock {
     # timeout should be more than enough here...
     PVE::Systemd::wait_for_unit_removed("$vmid.scope", 20);
 
-    my $cpuunits = get_cpuunits($conf);
+    my $cpuunits = PVE::CGroup::clamp_cpu_shares($conf->{cpuunits});
 
     my %run_params = (
        timeout => $statefile ? undef : $start_timeout,
@@ -5697,7 +5757,9 @@ sub vm_start_nolock {
     if (my $err = $@) {
        # deactivate volumes if start fails
        eval { PVE::Storage::deactivate_volumes($storecfg, $vollist); };
-       eval { PVE::QemuServer::PCI::remove_pci_reservation($pci_id_list) };
+       warn $@ if $@;
+       eval { cleanup_pci_devices($vmid, $conf) };
+       warn $@ if $@;
 
        die "start failed: $err";
     }
@@ -5862,6 +5924,24 @@ sub get_vm_volumes {
     return $vollist;
 }
 
+sub cleanup_pci_devices {
+    my ($vmid, $conf) = @_;
+
+    foreach my $key (keys %$conf) {
+       next if $key !~ m/^hostpci(\d+)$/;
+       my $hostpciindex = $1;
+       my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $hostpciindex);
+       my $d = parse_hostpci($conf->{$key});
+       if ($d->{mdev}) {
+           # NOTE: avoid PVE::SysFSTools::pci_cleanup_mdev_device as it requires PCI ID and we
+           # don't want to break ABI just for this two liner
+           my $dev_sysfs_dir = "/sys/bus/mdev/devices/$uuid";
+           PVE::SysFSTools::file_write("$dev_sysfs_dir/remove", "1") if -e $dev_sysfs_dir;
+       }
+    }
+    PVE::QemuServer::PCI::remove_pci_reservation($vmid);
+}
+
 sub vm_stop_cleanup {
     my ($storecfg, $vmid, $conf, $keepActive, $apply_pending_changes) = @_;
 
@@ -5893,20 +5973,7 @@ sub vm_stop_cleanup {
            unlink '/dev/shm/pve-shm-' . ($ivshmem->{name} // $vmid);
        }
 
-       my $ids = [];
-       foreach my $key (keys %$conf) {
-           next if $key !~ m/^hostpci(\d+)$/;
-           my $hostpciindex = $1;
-           my $d = parse_hostpci($conf->{$key});
-           my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $hostpciindex);
-
-           foreach my $pci (@{$d->{pciid}}) {
-               my $pciid = $pci->{id};
-               push @$ids, $pci->{id};
-               PVE::SysFSTools::pci_cleanup_mdev_device($pciid, $uuid);
-           }
-       }
-       PVE::QemuServer::PCI::remove_pci_reservation($ids);
+       cleanup_pci_devices($vmid, $conf);
 
        vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes;
     };
@@ -6652,6 +6719,7 @@ sub restore_proxmox_backup_archive {
     my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);
 
     my $repo = PVE::PBSClient::get_repository($scfg);
+    my $namespace = $scfg->{namespace};
 
     # This is only used for `pbs-restore` and the QEMU PBS driver (live-restore)
     my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
@@ -6842,28 +6910,36 @@ sub restore_proxmox_backup_archive {
        # these special drives are already restored before start
        delete $devinfo->{'drive-efidisk0'};
        delete $devinfo->{'drive-tpmstate0-backup'};
-       pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $repo, $keyfile, $pbs_backup_name);
+
+       my $pbs_opts = {
+           repo => $repo,
+           keyfile => $keyfile,
+           snapshot => $pbs_backup_name,
+           namespace => $namespace,
+       };
+       pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $pbs_opts);
 
        PVE::QemuConfig->remove_lock($vmid, "create");
     }
 }
 
 sub pbs_live_restore {
-    my ($vmid, $conf, $storecfg, $restored_disks, $repo, $keyfile, $snap) = @_;
+    my ($vmid, $conf, $storecfg, $restored_disks, $opts) = @_;
 
     print "starting VM for live-restore\n";
-    print "repository: '$repo', snapshot: '$snap'\n";
+    print "repository: '$opts->{repo}', snapshot: '$opts->{snapshot}'\n";
 
     my $pbs_backing = {};
     for my $ds (keys %$restored_disks) {
        $ds =~ m/^drive-(.*)$/;
        my $confname = $1;
        $pbs_backing->{$confname} = {
-           repository => $repo,
-           snapshot => $snap,
+           repository => $opts->{repo},
+           snapshot => $opts->{snapshot},
            archive => "$ds.img.fidx",
        };
-       $pbs_backing->{$confname}->{keyfile} = $keyfile if -e $keyfile;
+       $pbs_backing->{$confname}->{keyfile} = $opts->{keyfile} if -e $opts->{keyfile};
+       $pbs_backing->{$confname}->{namespace} = $opts->{namespace} if defined($opts->{namespace});
 
        my $drive = parse_drive($confname, $conf->{$confname});
        print "restoring '$ds' to '$drive->{file}'\n";
@@ -7886,24 +7962,6 @@ sub scsihw_infos {
     return ($maxdev, $controller, $controller_prefix);
 }
 
-sub windows_version {
-    my ($ostype) = @_;
-
-    return 0 if !$ostype;
-
-    my $winversion = 0;
-
-    if($ostype eq 'wxp' || $ostype eq 'w2k3' || $ostype eq 'w2k') {
-        $winversion = 5;
-    } elsif($ostype eq 'w2k8' || $ostype eq 'wvista') {
-        $winversion = 6;
-    } elsif ($ostype =~ m/^win(\d+)$/) {
-        $winversion = $1;
-    }
-
-    return $winversion;
-}
-
 sub resolve_dst_disk_format {
        my ($storecfg, $storeid, $src_volname, $format) = @_;
        my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);