]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
restore vma: add comment describing timeout
[qemu-server.git] / PVE / QemuServer.pm
index ab33aa375b23878711f910e973c17d6fe3ca125c..f7ee7686eb5ab5e53b395bdc560fb4c665f8357a 100644 (file)
@@ -34,6 +34,8 @@ 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::Mapping::PCI;
+use PVE::Mapping::USB;
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option parse_property_string);
 use PVE::ProcFSTools;
@@ -56,7 +58,7 @@ use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::Monitor qw(mon_cmd);
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
-use PVE::QemuServer::USB qw(parse_usb_device);
+use PVE::QemuServer::USB;
 
 my $have_sdn;
 eval {
@@ -558,7 +560,7 @@ EODESC
        verbose_description => <<EODESCR,
 Arbitrary arguments passed to kvm, for example:
 
-args: -no-reboot -no-hpet
+args: -no-reboot -smbios 'type=0,vendor=FOO'
 
 NOTE: this option is for experts only.
 EODESCR
@@ -758,6 +760,7 @@ my $cicustom_fmt = {
 };
 PVE::JSONSchema::register_format('pve-qm-cicustom', $cicustom_fmt);
 
+# any new option might need to be added to $cloudinitoptions in PVE::API2::Qemu
 my $confdesc_cloudinit = {
     citype => {
        optional => 1,
@@ -780,6 +783,12 @@ my $confdesc_cloudinit = {
            .' recommended. Use ssh keys instead. Also note that older cloud-init versions do not'
            .' support hashed passwords.',
     },
+    ciupgrade => {
+       optional => 1,
+       type => 'boolean',
+       description => 'cloud-init: do an automatic package upgrade after the first boot.',
+       default => 1,
+    },
     cicustom => {
        optional => 1,
        type => 'string',
@@ -830,7 +839,6 @@ while (my ($k, $v) = each %$confdesc) {
     PVE::JSONSchema::register_standard_option("pve-qm-$k", $v);
 }
 
-my $MAX_USB_DEVICES = 14;
 my $MAX_NETS = 32;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
@@ -1076,44 +1084,6 @@ sub verify_volume_id_or_absolute_path {
     return $volid;
 }
 
-my $usb_fmt = {
-    host => {
-       default_key => 1,
-       type => 'string', format => 'pve-qm-usb-device',
-       format_description => 'HOSTUSBDEVICE|spice',
-        description => <<EODESCR,
-The Host USB device or port or the value 'spice'. HOSTUSBDEVICE syntax is:
-
- 'bus-port(.port)*' (decimal numbers) or
- 'vendor_id:product_id' (hexadeciaml numbers) or
- 'spice'
-
-You can use the 'lsusb -t' command to list existing usb devices.
-
-NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such
-machines - use with special care.
-
-The value 'spice' can be used to add a usb redirection devices for spice.
-EODESCR
-    },
-    usb3 => {
-       optional => 1,
-       type => 'boolean',
-       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,
-    },
-};
-
-my $usbdesc = {
-    optional => 1,
-    type => 'string', format => $usb_fmt,
-    description => "Configure an USB device (n is 0 to 4, for machine version >= 7.1 and ostype"
-       ." l26 or windows > 7, n can be up to 14).",
-};
-PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
-
 my $serialdesc = {
        optional => 1,
        type => 'string',
@@ -1162,8 +1132,8 @@ for my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) {
     $confdesc->{$key} = $PVE::QemuServer::Drive::drivedesc_hash->{$key};
 }
 
-for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
-    $confdesc->{"usb$i"} = $usbdesc;
+for (my $i = 0; $i < $PVE::QemuServer::USB::MAX_USB_DEVICES; $i++)  {
+    $confdesc->{"usb$i"} = $PVE::QemuServer::USB::usbdesc;
 }
 
 my $boot_fmt = {
@@ -1552,6 +1522,17 @@ sub print_drivedevice_full {
        my $maxdev = ($drive->{interface} eq 'sata') ? $PVE::QemuServer::Drive::MAX_SATA_DISKS : 2;
        my $controller = int($drive->{index} / $maxdev);
        my $unit = $drive->{index} % $maxdev;
+
+       # machine type q35 only supports unit=0 for IDE rather than 2 units. This wasn't handled
+       # correctly before, so e.g. index=2 was mapped to controller=1,unit=0 rather than
+       # controller=2,unit=0. Note that odd indices never worked, as they would be mapped to
+       # unit=1, so to keep backwards compat for migration, it suffices to keep even ones as they
+       # were before. Move odd ones up by 2 where they don't clash.
+       if (PVE::QemuServer::Machine::machine_type_is_q35($conf) && $drive->{interface} eq 'ide') {
+           $controller += 2 * ($unit % 2);
+           $unit = 0;
+       }
+
        my $devicetype = ($drive->{media} && $drive->{media} eq 'cdrom') ? "cd" : "hd";
 
        $device = "ide-$devicetype";
@@ -1793,7 +1774,7 @@ sub print_netdevice_full {
     }
 
     if (min_version($machine_version, 7, 1) && $net->{model} eq 'virtio'){
-       $tmpstr .= ",rx_queue_size=1024,tx_queue_size=1024";
+       $tmpstr .= ",rx_queue_size=1024,tx_queue_size=256";
     }
 
     $tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ;
@@ -2239,17 +2220,6 @@ sub qemu_created_version_fixups {
     return;
 }
 
-PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
-sub verify_usb_device {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_usb_device($value);
-
-    return if $noerr;
-
-    die "unable to parse usb device\n";
-}
-
 # add JSON properties for create and set function
 sub json_config_properties {
     my ($prop, $with_disk_alloc) = @_;
@@ -2695,6 +2665,28 @@ sub check_local_resources {
     my ($conf, $noerr) = @_;
 
     my @loc_res = ();
+    my $mapped_res = [];
+
+    my $nodelist = PVE::Cluster::get_nodelist();
+    my $pci_map = PVE::Mapping::PCI::config();
+    my $usb_map = PVE::Mapping::USB::config();
+
+    my $missing_mappings_by_node = { map { $_ => [] } @$nodelist };
+
+    my $add_missing_mapping = sub {
+       my ($type, $key, $id) = @_;
+       for my $node (@$nodelist) {
+           my $entry;
+           if ($type eq 'pci') {
+               $entry = PVE::Mapping::PCI::get_node_mapping($pci_map, $id, $node);
+           } elsif ($type eq 'usb') {
+               $entry = PVE::Mapping::USB::get_node_mapping($usb_map, $id, $node);
+           }
+           if (!scalar($entry->@*)) {
+               push @{$missing_mappings_by_node->{$node}}, $key;
+           }
+       }
+    };
 
     push @loc_res, "hostusb" if $conf->{hostusb}; # old syntax
     push @loc_res, "hostpci" if $conf->{hostpci}; # old syntax
@@ -2702,7 +2694,21 @@ sub check_local_resources {
     push @loc_res, "ivshmem" if $conf->{ivshmem};
 
     foreach my $k (keys %$conf) {
-       next if $k =~ m/^usb/ && ($conf->{$k} =~ m/^spice(?![^,])/);
+       if ($k =~ m/^usb/) {
+           my $entry = parse_property_string('pve-qm-usb', $conf->{$k});
+           next if $entry->{host} =~ m/^spice$/i;
+           if ($entry->{mapping}) {
+               $add_missing_mapping->('usb', $k, $entry->{mapping});
+               push @$mapped_res, $k;
+           }
+       }
+       if ($k =~ m/^hostpci/) {
+           my $entry = parse_property_string('pve-qm-hostpci', $conf->{$k});
+           if ($entry->{mapping}) {
+               $add_missing_mapping->('pci', $k, $entry->{mapping});
+               push @$mapped_res, $k;
+           }
+       }
        # sockets are safe: they will recreated be on the target side post-migrate
        next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket');
        push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
@@ -2710,7 +2716,7 @@ sub check_local_resources {
 
     die "VM uses local resources\n" if scalar @loc_res && !$noerr;
 
-    return \@loc_res;
+    return wantarray ? (\@loc_res, $mapped_res, $missing_mappings_by_node) : \@loc_res;
 }
 
 # check if used storages are available on all nodes (use by migrate)
@@ -2867,7 +2873,7 @@ our $vmstatus_return_properties = {
        optional => 1,
     },
     qmpstatus => {
-       description => "QEMU QMP agent status.",
+       description => "VM run state from the 'query-status' QMP monitor command.",
        type => 'string',
        optional => 1,
     },
@@ -3735,7 +3741,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, $machine_version);
+       $conf, $bridges, $arch, $machine_type, $machine_version);
     push @$devices, @usbcontrollers if @usbcontrollers;
     my $vga = parse_vga($conf->{vga});
 
@@ -3769,15 +3775,15 @@ sub config_to_command {
     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, $vga, $winversion, $q35, $bridges, $arch, $machine_type, $bootorder);
+    my ($kvm_off, $gpu_passthrough, $legacy_igd, $pci_devices) = PVE::QemuServer::PCI::print_hostpci_devices(
+       $vmid, $conf, $devices, $vga, $winversion, $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, $bootorder, $machine_version);
+       $conf, $usb_dev_features, $bootorder, $machine_version);
     push @$devices, @usbdevices if @usbdevices;
 
     # serial devices
@@ -3796,7 +3802,7 @@ sub config_to_command {
            }
        } else {
            die "no such serial device\n" if ! -c $path;
-           push @$devices, '-chardev', "tty,id=serial$i,path=$path";
+           push @$devices, '-chardev', "serial,id=serial$i,path=$path";
            push @$devices, '-device', "isa-serial,chardev=serial$i";
        }
     }
@@ -3805,7 +3811,7 @@ sub config_to_command {
     for (my $i = 0; $i < $MAX_PARALLEL_PORTS; $i++)  {
        if (my $path = $conf->{"parallel$i"}) {
            die "no such parallel device\n" if ! -c $path;
-           my $devtype = $path =~ m!^/dev/usb/lp! ? 'tty' : 'parport';
+           my $devtype = $path =~ m!^/dev/usb/lp! ? 'serial' : 'parallel';
            push @$devices, '-chardev', "$devtype,id=parallel$i,path=$path";
            push @$devices, '-device', "isa-parallel,chardev=parallel$i";
        }
@@ -3817,7 +3823,9 @@ sub config_to_command {
        push @$devices, @$audio_devs;
     }
 
-    add_tpm_device($vmid, $devices, $conf);
+    # Add a TPM only if the VM is not a template,
+    # to support backing up template VMs even if the TPM disk is write-protected.
+    add_tpm_device($vmid, $devices, $conf) if (!PVE::QemuConfig->is_template($conf));
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -3848,7 +3856,7 @@ sub config_to_command {
 
     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;
+    push $machineFlags->@*, 'acpi=off' if defined($conf->{acpi}) && $conf->{acpi} == 0;
 
     push @$cmd, '-no-reboot' if  defined($conf->{reboot}) && $conf->{reboot} == 0;
 
@@ -3880,7 +3888,7 @@ sub config_to_command {
 
     if ($winversion >= 6) {
        push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
-       push @$cmd, '-no-hpet';
+       push @$machineFlags, 'hpet=off';
     }
 
     push @$rtcFlags, 'driftfix=slew' if $tdf;
@@ -4189,7 +4197,7 @@ sub config_to_command {
        push @$cmd, @$aa;
     }
 
-    return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
+    return wantarray ? ($cmd, $vollist, $spice_port, $pci_devices) : $cmd;
 }
 
 sub check_rng_source {
@@ -4648,12 +4656,8 @@ sub qemu_usb_hotplug {
        qemu_deviceadd($vmid, PVE::QemuServer::USB::print_qemu_xhci_controller($pciaddr));
     }
 
-    # print_usbdevice_full expects the parsed device
-    my $d = parse_usb_device($device->{host});
-    $d->{usb3} = $device->{usb3};
-
     # add the new one
-    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d, $arch, $machine_type);
+    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type);
 }
 
 sub qemu_cpu_hotplug {
@@ -4768,7 +4772,7 @@ sub qemu_block_resize {
 
     my $running = check_running($vmid);
 
-    $size = 0 if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
+    PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
 
     return if !$running;
 
@@ -4855,7 +4859,7 @@ sub foreach_volid {
     my $volhash = {};
 
     my $test_volid = sub {
-       my ($key, $drive, $snapname) = @_;
+       my ($key, $drive, $snapname, $pending) = @_;
 
        my $volid = $drive->{file};
        return if !$volid;
@@ -4870,12 +4874,18 @@ sub foreach_volid {
        $volhash->{$volid}->{shared} //= 0;
        $volhash->{$volid}->{shared} = 1 if $drive->{shared};
 
-       $volhash->{$volid}->{referenced_in_config} //= 0;
-       $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname);
+       $volhash->{$volid}->{is_unused} //= 0;
+       $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/;
+
+       $volhash->{$volid}->{is_attached} //= 0;
+       $volhash->{$volid}->{is_attached} = 1
+           if !$volhash->{$volid}->{is_unused} && !defined($snapname) && !$pending;
 
        $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
            if defined($snapname);
 
+       $volhash->{$volid}->{referenced_in_pending} = 1 if $pending;
+
        my $size = $drive->{size};
        $volhash->{$volid}->{size} //= $size if $size;
 
@@ -4885,9 +4895,6 @@ sub foreach_volid {
        $volhash->{$volid}->{is_tpmstate} //= 0;
        $volhash->{$volid}->{is_tpmstate} = 1 if $key eq 'tpmstate0';
 
-       $volhash->{$volid}->{is_unused} //= 0;
-       $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/;
-
        $volhash->{$volid}->{drivename} = $key if is_valid_drivename($key);
     };
 
@@ -4897,6 +4904,10 @@ sub foreach_volid {
     };
 
     PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $test_volid);
+
+    PVE::QemuConfig->foreach_volume_full($conf->{pending}, $include_opts, $test_volid, undef, 1)
+       if defined($conf->{pending}) && $conf->{pending}->%*;
+
     foreach my $snapname (keys %{$conf->{snapshots}}) {
        my $snap = $conf->{snapshots}->{$snapname};
        PVE::QemuConfig->foreach_volume_full($snap, $include_opts, $test_volid, $snapname);
@@ -4908,16 +4919,18 @@ sub foreach_volid {
 }
 
 my $fast_plug_option = {
+    'description' => 1,
+    'hookscript' => 1,
     'lock' => 1,
+    'migrate_downtime' => 1,
+    'migrate_speed' => 1,
     'name' => 1,
     'onboot' => 1,
+    'protection' => 1,
     'shares' => 1,
     'startup' => 1,
-    'description' => 1,
-    'protection' => 1,
-    'vmstatestorage' => 1,
-    'hookscript' => 1,
     'tags' => 1,
+    'vmstatestorage' => 1,
 };
 
 for my $opt (keys %$confdesc_cloudinit) {
@@ -5113,9 +5126,9 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt =~ m/^usb(\d+)$/) {
                my $index = $1;
                die "skip\n" if !$usb_hotplug;
-               my $d = eval { parse_property_string($usbdesc->{format}, $value) };
+               my $d = eval { parse_property_string('pve-qm-usb', $value) };
                my $id = $opt;
-               if ($d->{host} eq 'spice')  {
+               if ($d->{host} =~ m/^spice$/i)  {
                    $id = "usbredirdev$index";
                }
                qemu_usb_hotplug($storecfg, $conf, $vmid, $id, $d, $arch, $machine_type);
@@ -5192,7 +5205,7 @@ sub vmconfig_hotplug_pending {
     # unplug xhci controller if no usb device is left
     if ($usb_hotplug) {
        my $has_usb = 0;
-       for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) {
+       for (my $i = 0; $i < $PVE::QemuServer::USB::MAX_USB_DEVICES; $i++) {
            next if !defined($conf->{"usb$i"});
            $has_usb = 1;
            last;
@@ -5555,9 +5568,11 @@ sub vm_migrate_get_nbd_disks {
        my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
        return if $scfg->{shared};
 
+       my $format = qemu_img_format($scfg, $volname);
+
        # replicated disks re-use existing state via bitmap
        my $use_existing = $replicated_volumes->{$volid} ? 1 : 0;
-       $local_volumes->{$ds} = [$volid, $storeid, $volname, $drive, $use_existing];
+       $local_volumes->{$ds} = [$volid, $storeid, $drive, $use_existing, $format];
     });
     return $local_volumes;
 }
@@ -5568,7 +5583,7 @@ sub vm_migrate_alloc_nbd_disks {
 
     my $nbd = {};
     foreach my $opt (sort keys %$source_volumes) {
-       my ($volid, $storeid, $volname, $drive, $use_existing, $format) = @{$source_volumes->{$opt}};
+       my ($volid, $storeid, $drive, $use_existing, $format) = @{$source_volumes->{$opt}};
 
        if ($use_existing) {
            $nbd->{$opt}->{drivestr} = print_drive($drive);
@@ -5577,29 +5592,13 @@ sub vm_migrate_alloc_nbd_disks {
            next;
        }
 
-       # storage mapping + volname = regular migration
-       # storage mapping + format = remote migration
+       $storeid = PVE::JSONSchema::map_id($storagemap, $storeid);
+
        # order of precedence, filtered by whether storage supports it:
        # 1. explicit requested format
-       # 2. format of current volume
-       # 3. default format of storage
-       if (!$storagemap->{identity}) {
-           $storeid = PVE::JSONSchema::map_id($storagemap, $storeid);
-           my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
-           if (!$format || !grep { $format eq $_ } @$validFormats) {
-               if ($volname) {
-                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-                   my $fileFormat = qemu_img_format($scfg, $volname);
-                   $format = $fileFormat
-                       if grep { $fileFormat eq $_ } @$validFormats;
-               }
-               $format //= $defFormat;
-           }
-       } else {
-           # can't happen for remote migration, so $volname is always defined
-           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-           $format = qemu_img_format($scfg, $volname);
-       }
+       # 2. default format of storage
+       my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+       $format = $defFormat if !$format || !grep { $format eq $_ } $validFormats->@*;
 
        my $size = $drive->{size} / 1024;
        my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, $size);
@@ -5746,7 +5745,7 @@ sub vm_start_nolock {
        print "Resuming suspended VM\n";
     }
 
-    my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid,
+    my ($cmd, $vollist, $spice_port, $pci_devices) = config_to_command($storecfg, $vmid,
        $conf, $defaults, $forcemachine, $forcecpu, $params->{'pbs-backing'});
 
     my $migration_ip;
@@ -5831,37 +5830,43 @@ sub vm_start_nolock {
 
     my $start_timeout = $params->{timeout} // config_aware_timeout($conf, $resume);
 
-    my $pci_devices = {}; # host pci devices
-    for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++)  {
-       my $dev = $conf->{"hostpci$i"} or next;
-       $pci_devices->{$i} = parse_hostpci($dev);
+    my $pci_reserve_list = [];
+    for my $device (values $pci_devices->%*) {
+       next if $device->{mdev}; # we don't reserve for mdev devices
+       push $pci_reserve_list->@*, map { $_->{id} } $device->{ids}->@*;
     }
 
-    # do not reserve pciid for mediated devices, sysfs will error out for duplicate assignment
-    my $real_pci_devices = [ grep { !(defined($_->{mdev}) && scalar($_->{pciid}->@*) == 1) } values $pci_devices->%* ];
-
-    # map to a flat list of pci ids
-    my $pci_id_list = [ map { $_->{id} } map { $_->{pciid}->@* } $real_pci_devices->@* ];
-
     # reserve all PCI IDs before actually doing anything with them
-    PVE::QemuServer::PCI::reserve_pci_usage($pci_id_list, $vmid, $start_timeout);
+    PVE::QemuServer::PCI::reserve_pci_usage($pci_reserve_list, $vmid, $start_timeout);
 
     eval {
        my $uuid;
        for my $id (sort keys %$pci_devices) {
            my $d = $pci_devices->{$id};
-           for my $dev ($d->{pciid}->@*) {
-               my $info = PVE::QemuServer::PCI::prepare_pci_device($vmid, $dev->{id}, $id, $d->{mdev});
-
-               # nvidia grid needs the qemu parameter '-uuid' set
-               # use smbios uuid or mdev uuid as fallback for that
-               if ($d->{mdev} && !defined($uuid) && $info->{vendor} eq '10de') {
-                   if (defined($conf->{smbios1})) {
-                       my $smbios_conf = parse_smbios1($conf->{smbios1});
-                       $uuid = $smbios_conf->{uuid} if defined($smbios_conf->{uuid});
-                   }
-                   $uuid = PVE::QemuServer::PCI::generate_mdev_uuid($vmid, $id) if !defined($uuid);
+           my ($index) = ($id =~ m/^hostpci(\d+)$/);
+
+           my $chosen_mdev;
+           for my $dev ($d->{ids}->@*) {
+               my $info = eval { PVE::QemuServer::PCI::prepare_pci_device($vmid, $dev->{id}, $index, $d->{mdev}) };
+               if ($d->{mdev}) {
+                   warn $@ if $@;
+                   $chosen_mdev = $info;
+                   last if $chosen_mdev; # if successful, we're done
+               } else {
+                   die $@ if $@;
+               }
+           }
+
+           next if !$d->{mdev};
+           die "could not create mediated device\n" if !defined($chosen_mdev);
+
+           # nvidia grid needs the uuid of the mdev as qemu parameter
+           if (!defined($uuid) && $chosen_mdev->{vendor} =~ m/^(0x)?10de$/) {
+               if (defined($conf->{smbios1})) {
+                   my $smbios_conf = parse_smbios1($conf->{smbios1});
+                   $uuid = $smbios_conf->{uuid} if defined($smbios_conf->{uuid});
                }
+               $uuid = PVE::QemuServer::PCI::generate_mdev_uuid($vmid, $index) if !defined($uuid);
            }
        }
        push @$cmd, '-uuid', $uuid if defined($uuid);
@@ -5874,9 +5879,10 @@ sub vm_start_nolock {
 
     PVE::Storage::activate_volumes($storecfg, $vollist);
 
-    eval {
-       run_command(['/bin/systemctl', 'stop', "$vmid.scope"], outfunc => sub{}, errfunc => sub{});
-    };
+
+    my %silence_std_outs = (outfunc => sub {}, errfunc => sub {});
+    eval { run_command(['/bin/systemctl', 'reset-failed', "$vmid.scope"], %silence_std_outs) };
+    eval { run_command(['/bin/systemctl', 'stop', "$vmid.scope"], %silence_std_outs) };
     # Issues with the above 'stop' not being fully completed are extremely rare, a very low
     # timeout should be more than enough here...
     PVE::Systemd::wait_for_unit_removed("$vmid.scope", 20);
@@ -5919,7 +5925,7 @@ sub vm_start_nolock {
            PVE::Systemd::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %systemd_properties);
 
            my $tpmpid;
-           if (my $tpm = $conf->{tpmstate0}) {
+           if ((my $tpm = $conf->{tpmstate0}) && !PVE::QemuConfig->is_template($conf)) {
                # start the TPM emulator so QEMU can connect on start
                $tpmpid = start_swtpm($storecfg, $vmid, $tpm, $migratedfrom);
            }
@@ -5976,7 +5982,7 @@ sub vm_start_nolock {
 
     # re-reserve all PCI IDs now that we can know the actual VM PID
     my $pid = PVE::QemuServer::Helpers::vm_running_locally($vmid);
-    eval { PVE::QemuServer::PCI::reserve_pci_usage($pci_id_list, $vmid, undef, $pid) };
+    eval { PVE::QemuServer::PCI::reserve_pci_usage($pci_reserve_list, $vmid, undef, $pid) };
     warn $@ if $@;
 
     if (defined($res->{migrate})) {
@@ -6171,9 +6177,7 @@ sub cleanup_pci_devices {
 
            # some nvidia vgpu driver versions want to clean the mdevs up themselves, and error
            # out when we do it first. so wait for 10 seconds and then try it
-           my $pciid = $d->{pciid}->[0]->{id};
-           my $info = PVE::SysFSTools::pci_device_info("$pciid");
-           if ($info->{vendor} eq '10de') {
+           if ($d->{ids}->[0]->[0]->{vendor} =~ m/^(0x)?10de$/) {
                sleep 10;
            }
 
@@ -6506,6 +6510,53 @@ sub vm_sendkey {
     });
 }
 
+sub check_bridge_access {
+    my ($rpcenv, $authuser, $conf) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    for my $opt (sort keys $conf->%*) {
+       next if $opt !~ m/^net\d+$/;
+       my $net = parse_net($conf->{$opt});
+       my ($bridge, $tag, $trunks) = $net->@{'bridge', 'tag', 'trunks'};
+       PVE::GuestHelpers::check_vnet_access($rpcenv, $authuser, $bridge, $tag, $trunks);
+    }
+    return 1;
+};
+
+sub check_mapping_access {
+    my ($rpcenv, $user, $conf) = @_;
+
+    for my $opt (keys $conf->%*) {
+       if ($opt =~ m/^usb\d+$/) {
+           my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $conf->{$opt});
+           if (my $host = $device->{host}) {
+               die "only root can set '$opt' config for real devices\n"
+                   if $host !~ m/^spice$/i && $user ne 'root@pam';
+           } elsif ($device->{mapping}) {
+               $rpcenv->check_full($user, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
+           } else {
+               die "either 'host' or 'mapping' must be set.\n";
+           }
+       } elsif ($opt =~ m/^hostpci\d+$/) {
+           my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $conf->{$opt});
+           if ($device->{host}) {
+               die "only root can set '$opt' config for non-mapped devices\n" if $user ne 'root@pam';
+           } elsif ($device->{mapping}) {
+               $rpcenv->check_full($user, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
+           } else {
+               die "either 'host' or 'mapping' must be set.\n";
+           }
+       }
+   }
+};
+
+sub check_restore_permissions {
+    my ($rpcenv, $user, $conf) = @_;
+
+    check_bridge_access($rpcenv, $user, $conf);
+    check_mapping_access($rpcenv, $user, $conf);
+}
 # vzdump restore implementaion
 
 sub tar_archive_read_firstfile {
@@ -6822,7 +6873,7 @@ my $restore_destroy_volumes = sub {
     }
 };
 
-my $restore_merge_config = sub {
+sub restore_merge_config {
     my ($filename, $backup_conf_raw, $override_conf) = @_;
 
     my $backup_conf = parse_vm_config($filename, $backup_conf_raw);
@@ -6831,7 +6882,7 @@ my $restore_merge_config = sub {
     }
 
     return $backup_conf;
-};
+}
 
 sub scan_volids {
     my ($cfg, $vmid) = @_;
@@ -7149,7 +7200,8 @@ sub restore_proxmox_backup_archive {
        $new_conf_raw .= "\nlock: create";
     }
 
-    my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $options->{override_conf});
+    my $new_conf = restore_merge_config($conffile, $new_conf_raw, $options->{override_conf});
+    check_restore_permissions($rpcenv, $user, $new_conf);
     PVE::QemuConfig->write_config($vmid, $new_conf);
 
     eval { rescan($vmid, 1); };
@@ -7315,7 +7367,7 @@ sub restore_vma_archive {
     $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
 
     my $oldtimeout;
-    my $timeout = 5;
+    my $timeout = 5; # for reading the VMA header - might hang with a corrupted one
 
     my $devinfo = {}; # info about drives included in backup
     my $virtdev_hash = {}; # info about allocated drives
@@ -7431,14 +7483,11 @@ sub restore_vma_archive {
                $devinfo->{$devname} = { size => $size, dev_id => $dev_id };
            } elsif ($line =~ m/^CTIME: /) {
                # we correctly received the vma config, so we can disable
-               # the timeout now for disk allocation (set to 10 minutes, so
-               # that we always timeout if something goes wrong)
-               alarm(600);
+               # the timeout now for disk allocation
+               alarm($oldtimeout || 0);
+               $oldtimeout = undef;
                &$print_devmap();
                print $fifofh "done\n";
-               my $tmp = $oldtimeout || 0;
-               $oldtimeout = undef;
-               alarm($tmp);
                close($fifofh);
                $fifofh = undef;
            }
@@ -7462,7 +7511,8 @@ sub restore_vma_archive {
        die $err;
     }
 
-    my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $opts->{override_conf});
+    my $new_conf = restore_merge_config($conffile, $new_conf_raw, $opts->{override_conf});
+    check_restore_permissions($rpcenv, $user, $new_conf);
     PVE::QemuConfig->write_config($vmid, $new_conf);
 
     eval { rescan($vmid, 1); };
@@ -8576,6 +8626,10 @@ sub add_nets_bridge_fdb {
        }
 
        my $bridge = $net->{bridge};
+       if (!$bridge) {
+           log_warn("Interface '$iface' not attached to any bridge.");
+           next;
+       }
        if ($have_sdn) {
            PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $bridge, $net->{firewall});
        } elsif (-d "/sys/class/net/$bridge/bridge") { # avoid fdb management with OVS for now