]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix #2862: properly backup (all) VM templates
[qemu-server.git] / PVE / QemuServer.pm
index 37c73208e0d1071b2d17a2f8a0734641a6f43128..a5ee8e2a390e746e53edefb42965222ae1961b43 100644 (file)
@@ -26,7 +26,7 @@ use Time::HiRes qw(gettimeofday);
 use URI::Escape;
 use UUID;
 
-use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
 use PVE::DataCenterConfig;
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);
@@ -37,7 +37,7 @@ use PVE::RPCEnvironment;
 use PVE::Storage;
 use PVE::SysFSTools;
 use PVE::Systemd;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE);
+use PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE);
 
 use PVE::QMPClient;
 use PVE::QemuConfig;
@@ -48,7 +48,7 @@ use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdr
 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);
+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);
 
 my $have_sdn;
@@ -768,7 +768,6 @@ while (my ($k, $v) = each %$confdesc) {
 
 my $MAX_USB_DEVICES = 5;
 my $MAX_NETS = 32;
-my $MAX_HOSTPCI_DEVICES = 16;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
 my $MAX_NUMA = 8;
@@ -884,6 +883,12 @@ my $net_fmt = {
        description => 'Whether this interface should be disconnected (like pulling the plug).',
        optional => 1,
     },
+    mtu => {
+       type => 'integer',
+       minimum => 1, maximum => 65520,
+       description => "Force MTU, for VirtIO only. Set to '1' to use the bridge MTU",
+       optional => 1,
+    },
 };
 
 my $netdesc = {
@@ -1005,76 +1010,6 @@ my $usbdesc = {
 };
 PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
 
-my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
-my $hostpci_fmt = {
-    host => {
-       default_key => 1,
-       type => 'string',
-       pattern => qr/$PCIRE(;$PCIRE)*/,
-       format_description => 'HOSTPCIID[;HOSTPCIID2...]',
-       description => <<EODESCR,
-Host PCI device pass through. The PCI ID of a host's PCI device or a list
-of PCI virtual functions of the host. HOSTPCIID syntax is:
-
-'bus:dev.func' (hexadecimal numbers)
-
-You can us the 'lspci' command to list existing PCI devices.
-EODESCR
-    },
-    rombar => {
-       type => 'boolean',
-        description =>  "Specify whether or not the device's ROM will be visible in the guest's memory map.",
-       optional => 1,
-       default => 1,
-    },
-    romfile => {
-        type => 'string',
-        pattern => '[^,;]+',
-        format_description => 'string',
-        description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
-        optional => 1,
-    },
-    pcie => {
-       type => 'boolean',
-        description =>  "Choose the PCI-express bus (needs the 'q35' machine model).",
-       optional => 1,
-       default => 0,
-    },
-    'x-vga' => {
-       type => 'boolean',
-        description =>  "Enable vfio-vga device support.",
-       optional => 1,
-       default => 0,
-    },
-    'mdev' => {
-       type => 'string',
-        format_description => 'string',
-       pattern => '[^/\.:]+',
-       optional => 1,
-       description => <<EODESCR
-The type of mediated device to use.
-An instance of this type will be created on startup of the VM and
-will be cleaned up when the VM stops.
-EODESCR
-    }
-};
-PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
-
-my $hostpcidesc = {
-        optional => 1,
-        type => 'string', format => 'pve-qm-hostpci',
-        description => "Map host PCI devices into guest.",
-       verbose_description =>  <<EODESCR,
-Map host PCI devices into guest.
-
-NOTE: This option allows direct access to host hardware. So it is no longer
-possible to migrate such machines - use with special care.
-
-CAUTION: Experimental! User reported problems with this option.
-EODESCR
-};
-PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
-
 my $serialdesc = {
        optional => 1,
        type => 'string',
@@ -1113,8 +1048,8 @@ for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++)  {
     $confdesc->{"serial$i"} = $serialdesc;
 }
 
-for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
-    $confdesc->{"hostpci$i"} = $hostpcidesc;
+for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++)  {
+    $confdesc->{"hostpci$i"} = $PVE::QemuServer::PCI::hostpcidesc;
 }
 
 for my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) {
@@ -1593,6 +1528,22 @@ sub print_netdevice_full {
     }
     $tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ;
 
+    if (my $mtu = $net->{mtu}) {
+       if ($net->{model} eq 'virtio' && $net->{bridge}) {
+           my $bridge_mtu = PVE::Network::read_bridge_mtu($net->{bridge});
+           if ($mtu == 1) {
+                $mtu = $bridge_mtu;
+           } elsif ($mtu < 576) {
+               die "netdev $netid: MTU '$mtu' is smaller than the IP minimum MTU '576'\n";
+           } elsif ($mtu > $bridge_mtu) {
+               die "netdev $netid: MTU '$mtu' is bigger than the bridge MTU '$bridge_mtu'\n";
+           }
+           $tmpstr .= ",host_mtu=$mtu";
+       } else {
+           warn "WARN: netdev $netid: ignoring MTU '$mtu', not using VirtIO or no bridge configured.\n";
+       }
+    }
+
     if ($use_old_bios_files) {
        my $romfile;
        if ($device eq 'virtio-net-pci') {
@@ -1697,6 +1648,11 @@ sub print_vga_device {
        $memory = ",ram_size=67108864,vram_size=33554432";
     }
 
+    my $edidoff = "";
+    if ($type eq 'VGA' && windows_version($conf->{ostype})) {
+       $edidoff=",edid=off" if (!defined($conf->{bios}) || $conf->{bios} ne 'ovmf');
+    }
+
     my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
     my $vgaid = "vga" . ($id // '');
     my $pciaddr;
@@ -1708,7 +1664,7 @@ sub print_vga_device {
        $pciaddr = print_pci_addr($vgaid, $bridges, $arch, $machine);
     }
 
-    return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}";
+    return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}${edidoff}";
 }
 
 sub parse_number_sets {
@@ -1734,23 +1690,6 @@ sub parse_numa {
     return $res;
 }
 
-sub parse_hostpci {
-    my ($value) = @_;
-
-    return undef if !$value;
-
-    my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);
-
-    my @idlist = split(/;/, $res->{host});
-    delete $res->{host};
-    foreach my $id (@idlist) {
-       my $devs = PVE::SysFSTools::lspci($id);
-       die "no PCI device found for '$id'\n" if !scalar(@$devs);
-       push @{$res->{pciid}}, @$devs;
-    }
-    return $res;
-}
-
 # netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate=<mbps>
 sub parse_net {
     my ($data) = @_;
@@ -2752,19 +2691,22 @@ sub conf_has_audio {
 }
 
 sub audio_devs {
-    my ($audio, $audiopciaddr) = @_;
+    my ($audio, $audiopciaddr, $machine_version) = @_;
 
     my $devs = [];
 
     my $id = $audio->{dev_id};
-    my $audiodev = "audiodev=$audio->{backend_id}";
+    my $audiodev = "";
+    if (min_version($machine_version, 4, 2)) {
+       $audiodev = ",audiodev=$audio->{backend_id}";
+    }
 
     if ($audio->{dev} eq 'AC97') {
-       push @$devs, '-device', "AC97,id=${id}${audiopciaddr},$audiodev";
+       push @$devs, '-device', "AC97,id=${id}${audiopciaddr}$audiodev";
     } elsif ($audio->{dev} =~ /intel\-hda$/) {
        push @$devs, '-device', "$audio->{dev},id=${id}${audiopciaddr}";
-       push @$devs, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0,$audiodev";
-       push @$devs, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1,$audiodev";
+       push @$devs, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0$audiodev";
+       push @$devs, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1$audiodev";
     } else {
        die "unkown audio device '$audio->{dev}', implement me!";
     }
@@ -3084,7 +3026,8 @@ sub config_to_command {
        die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code;
 
        my ($path, $format);
-       if (my $d = parse_drive('efidisk0', $conf->{efidisk0})) {
+       if (my $efidisk = $conf->{efidisk0}) {
+           my $d = parse_drive('efidisk0', $efidisk);
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
            $format = $d->{format};
            if ($storeid) {
@@ -3163,77 +3106,9 @@ sub config_to_command {
        push @$devices, '-device', $kbd if defined($kbd);
     }
 
-    my $kvm_off = 0;
-    my $gpu_passthrough;
-
-    # host pci devices
-    for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
-       my $id = "hostpci$i";
-       my $d = parse_hostpci($conf->{$id});
-       next if !$d;
-
-       if (my $pcie = $d->{pcie}) {
-           die "q35 machine model is not enabled" if !$q35;
-           # win7 wants to have the pcie devices directly on the pcie bus
-           # instead of in the root port
-           if ($winversion == 7) {
-               $pciaddr = print_pcie_addr("${id}bus0");
-           } else {
-               # add more root ports if needed, 4 are present by default
-               # by pve-q35 cfgs, rest added here on demand.
-               if ($i > 3) {
-                   push @$devices, '-device', print_pcie_root_port($i);
-               }
-               $pciaddr = print_pcie_addr($id);
-           }
-       } else {
-           $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type);
-       }
-
-       my $xvga = '';
-       if ($d->{'x-vga'}) {
-           $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');
-           $kvm_off = 1;
-           $vga->{type} = 'none' if !defined($conf->{vga});
-           $gpu_passthrough = 1;
-       }
-
-       my $pcidevices = $d->{pciid};
-       my $multifunction = 1 if @$pcidevices > 1;
-
-       my $sysfspath;
-       if ($d->{mdev} && scalar(@$pcidevices) == 1) {
-           my $pci_id = $pcidevices->[0]->{id};
-           my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
-           $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid";
-       } elsif ($d->{mdev}) {
-           warn "ignoring mediated device '$id' with multifunction device\n";
-       }
-
-       my $j=0;
-       foreach my $pcidevice (@$pcidevices) {
-           my $devicestr = "vfio-pci";
-
-           if ($sysfspath) {
-               $devicestr .= ",sysfsdev=$sysfspath";
-           } else {
-               $devicestr .= ",host=$pcidevice->{id}";
-           }
-
-           my $mf_addr = $multifunction ? ".$j" : '';
-           $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";
-
-           if ($j == 0) {
-               $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
-               $devicestr .= "$xvga";
-               $devicestr .= ",multifunction=on" if $multifunction;
-               $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
-           }
-
-           push @$devices, '-device', $devicestr;
-           $j++;
-       }
-    }
+    # 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);
 
     # usb devices
     my $usb_dev_features = {};
@@ -3275,7 +3150,7 @@ sub config_to_command {
 
     if (min_version($machine_version, 4, 0) && (my $audio = conf_has_audio($conf))) {
        my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type);
-       my $audio_devs = audio_devs($audio, $audiopciaddr);
+       my $audio_devs = audio_devs($audio, $audiopciaddr, $machine_version);
        push @$devices, @$audio_devs;
     }
 
@@ -3387,20 +3262,16 @@ sub config_to_command {
 
     my $rng = parse_rng($conf->{rng0}) if $conf->{rng0};
     if ($rng && &$version_guard(4, 1, 2)) {
+       check_rng_source($rng->{source});
+
        my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
        my $period = $rng->{period} // $rng_fmt->{period}->{default};
-
        my $limiter_str = "";
        if ($max_bytes) {
            $limiter_str = ",max-bytes=$max_bytes,period=$period";
        }
 
-       # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
-       die "cannot create VirtIO RNG device: source file '$rng->{source}' doesn't exist\n"
-           if ! -e $rng->{source};
-
        my $rng_addr = print_pci_addr("rng0", $bridges, $arch, $machine_type);
-
        push @$devices, '-object', "rng-random,filename=$rng->{source},id=rng0";
        push @$devices, '-device', "virtio-rng-pci,rng=rng0$limiter_str$rng_addr";
     }
@@ -3536,6 +3407,8 @@ sub config_to_command {
         }
 
        my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive);
+       $drive_cmd .= ',readonly' if PVE::QemuConfig->is_template($conf);
+
        push @$devices, '-drive',$drive_cmd;
        push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type);
     });
@@ -3592,7 +3465,13 @@ sub config_to_command {
 
     for my $k (sort {$b cmp $a} keys %$bridges) {
        next if $q35 && $k < 4; # q35.cfg already includes bridges up to 3
-       $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type);
+
+       my $k_name = $k;
+       if ($k == 2 && $legacy_igd) {
+           $k_name = "$k-igd";
+       }
+       $pciaddr = print_pci_addr("pci.$k_name", undef, $arch, $machine_type);
+
        my $devstr = "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr";
        if ($q35) {
            # add after -readconfig pve-q35.cfg
@@ -3634,6 +3513,24 @@ sub config_to_command {
     return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
 }
 
+sub check_rng_source {
+    my ($source) = @_;
+
+    # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
+    die "cannot create VirtIO RNG device: source file '$source' doesn't exist\n"
+       if ! -e $source;
+
+    my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';
+    if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {
+       # Needs to abort, otherwise QEMU crashes on first rng access.
+       # Note that rng_current cannot be changed to 'none' manually, so
+       # once the VM is past this point, it is no longer an issue.
+       die "Cannot start VM with passed-through RNG device: '/dev/hwrng'"
+           . " exists, but '$rng_current' is set to 'none'. Ensure that"
+           . " a compatible hardware-RNG is attached to the host.\n";
+    }
+}
+
 sub spice_port {
     my ($vmid) = @_;
 
@@ -4047,6 +3944,14 @@ sub qemu_netdevadd {
     my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
     my %options =  split(/[=,]/, $netdev);
 
+    if (defined(my $vhost = $options{vhost})) {
+       $options{vhost} = JSON::boolean(PVE::JSONSchema::parse_boolean($vhost));
+    }
+
+    if (defined(my $queues = $options{queues})) {
+       $options{queues} = $queues + 0;
+    }
+
     mon_cmd($vmid, "netdev_add",  %options);
     return 1;
 }
@@ -4303,40 +4208,46 @@ sub foreach_volid {
     my $volhash = {};
 
     my $test_volid = sub {
-       my ($volid, $is_cdrom, $replicate, $shared, $snapname, $size) = @_;
+       my ($key, $drive, $snapname) = @_;
 
+       my $volid = $drive->{file};
        return if !$volid;
 
        $volhash->{$volid}->{cdrom} //= 1;
-       $volhash->{$volid}->{cdrom} = 0 if !$is_cdrom;
+       $volhash->{$volid}->{cdrom} = 0 if !drive_is_cdrom($drive);
 
+       my $replicate = $drive->{replicate} // 1;
        $volhash->{$volid}->{replicate} //= 0;
        $volhash->{$volid}->{replicate} = 1 if $replicate;
 
        $volhash->{$volid}->{shared} //= 0;
-       $volhash->{$volid}->{shared} = 1 if $shared;
+       $volhash->{$volid}->{shared} = 1 if $drive->{shared};
 
        $volhash->{$volid}->{referenced_in_config} //= 0;
        $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname);
 
        $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
            if defined($snapname);
-       $volhash->{$volid}->{size} = $size if $size;
+
+       my $size = $drive->{size};
+       $volhash->{$volid}->{size} //= $size if $size;
+
+       $volhash->{$volid}->{is_vmstate} //= 0;
+       $volhash->{$volid}->{is_vmstate} = 1 if $key eq 'vmstate';
+
+       $volhash->{$volid}->{is_unused} //= 0;
+       $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/;
     };
 
-    PVE::QemuConfig->foreach_volume($conf, sub {
-       my ($ds, $drive) = @_;
-       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef, $drive->{size});
-    });
+    my $include_opts = {
+       extra_keys => ['vmstate'],
+       include_unused => 1,
+    };
 
+    PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $test_volid);
     foreach my $snapname (keys %{$conf->{snapshots}}) {
        my $snap = $conf->{snapshots}->{$snapname};
-       $test_volid->($snap->{vmstate}, 0, 1, $snapname);
-       $volhash->{$snap->{vmstate}}->{is_vmstate} = 1 if $snap->{vmstate};
-       PVE::QemuConfig->foreach_volume($snap, sub {
-           my ($ds, $drive) = @_;
-           $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, $snapname);
-        });
+       PVE::QemuConfig->foreach_volume_full($snap, $include_opts, $test_volid, $snapname);
     }
 
     foreach my $volid (keys %$volhash) {
@@ -4876,12 +4787,15 @@ sub vm_start {
     return PVE::QemuConfig->lock_config($vmid, sub {
        my $conf = PVE::QemuConfig->load_config($vmid, $migrate_opts->{migratedfrom});
 
-       die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
+       die "you can't start a vm if it's a template\n"
+           if !$params->{skiptemplate} && PVE::QemuConfig->is_template($conf);
 
-       $params->{resume} = PVE::QemuConfig->has_lock($conf, 'suspended');
+       my $has_suspended_lock = PVE::QemuConfig->has_lock($conf, 'suspended');
 
        PVE::QemuConfig->check_lock($conf)
-           if !($params->{skiplock} || $params->{resume});
+           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});
 
@@ -4903,6 +4817,7 @@ sub vm_start {
 # params:
 #   statefile => 'tcp', 'unix' for migration or path/volid for RAM state
 #   skiplock => 0/1, skip checking for config lock
+#   skiptemplate => 0/1, skip checking whether VM is template
 #   forcemachine => to force Qemu machine (rollback/migration)
 #   forcecpu => a QEMU '-cpu' argument string to override get_cpu_options
 #   timeout => in seconds
@@ -5038,7 +4953,7 @@ sub vm_start_nolock {
     }
 
     # host pci devices
-    for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
+    for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++)  {
       my $d = parse_hostpci($conf->{"hostpci$i"});
       next if !$d;
       my $pcidevices = $d->{pciid};
@@ -5379,7 +5294,11 @@ sub _do_vm_stop {
            return;
        }
     } else {
-       if ($force) {
+       if (!check_running($vmid, $nocheck)) {
+           warn "Unexpected: VM shutdown command failed, but VM not running anymore..\n";
+           return;
+       }
+       if ($force) {
            warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
            kill 15, $pid;
        } else {
@@ -5627,28 +5546,12 @@ sub tar_restore_cleanup {
 sub restore_file_archive {
     my ($archive, $vmid, $user, $opts) = @_;
 
-    my $format = $opts->{format};
-    my $comp;
-
-    if ($archive =~ m/\.tgz$/ || $archive =~ m/\.tar\.gz$/) {
-       $format = 'tar' if !$format;
-       $comp = 'gzip';
-    } elsif ($archive =~ m/\.tar$/) {
-       $format = 'tar' if !$format;
-    } elsif ($archive =~ m/.tar.lzo$/) {
-       $format = 'tar' if !$format;
-       $comp = 'lzop';
-    } elsif ($archive =~ m/\.vma$/) {
-       $format = 'vma' if !$format;
-    } elsif ($archive =~ m/\.vma\.gz$/) {
-       $format = 'vma' if !$format;
-       $comp = 'gzip';
-    } elsif ($archive =~ m/\.vma\.lzo$/) {
-       $format = 'vma' if !$format;
-       $comp = 'lzop';
-    } else {
-       $format = 'vma' if !$format; # default
-    }
+    return restore_vma_archive($archive, $vmid, $user, $opts)
+       if $archive eq '-';
+
+    my $info = PVE::Storage::archive_info($archive);
+    my $format = $opts->{format} // $info->{format};
+    my $comp = $info->{compression};
 
     # try to detect archive format
     if ($format eq 'tar') {
@@ -5739,12 +5642,13 @@ my $parse_backup_hints = sub {
            my $drive = parse_drive($virtdev, $2);
            if (drive_is_cloudinit($drive)) {
                my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
+               $storeid = $options->{storage} if defined ($options->{storage});
                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
                my $format = qemu_img_format($scfg, $volname); # has 'raw' fallback
 
                $virtdev_hash->{$virtdev} = {
                    format => $format,
-                   storeid => $options->{storage} // $storeid,
+                   storeid => $storeid,
                    size => PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE,
                    is_cloudinit => 1,
                };
@@ -5911,7 +5815,7 @@ sub update_disk_config {
     my ($vmid, $conf, $volid_hash) = @_;
 
     my $changes;
-    my $prefix = "VM $vmid:";
+    my $prefix = "VM $vmid";
 
     # used and unused disks
     my $referenced = {};
@@ -5939,11 +5843,11 @@ sub update_disk_config {
        return if drive_is_cdrom($drive);
        return if !$volid_hash->{$volid};
 
-       my ($updated, $old_size, $new_size) = PVE::QemuServer::Drive::update_disksize($drive, $volid_hash);
+       my ($updated, $msg) = PVE::QemuServer::Drive::update_disksize($drive, $volid_hash->{$volid}->{size});
        if (defined($updated)) {
            $changes = 1;
            $conf->{$opt} = print_drive($updated);
-           print "$prefix size of disk '$volid' ($opt) updated from $old_size to $new_size\n";
+           print "$prefix ($opt): $msg\n";
        }
     });
 
@@ -6042,8 +5946,11 @@ sub restore_proxmox_backup_archive {
     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";
+
+    # This is only used for `pbs-restore`!
     my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
     local $ENV{PBS_PASSWORD} = $password;
     local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);
@@ -6124,7 +6031,7 @@ sub restore_proxmox_backup_archive {
        }
 
        my $fh = IO::File->new($cfgfn, "r") ||
-           "unable to read qemu-server.conf - $!\n";
+           die "unable to read qemu-server.conf - $!\n";
 
        my $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $storecfg, $fh, $devinfo, $options);
 
@@ -6146,6 +6053,7 @@ sub restore_proxmox_backup_archive {
 
            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,
@@ -6155,6 +6063,9 @@ sub restore_proxmox_backup_archive {
                '--verbose',
                ];
 
+           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';
            }
@@ -6235,14 +6146,9 @@ sub restore_vma_archive {
     }
 
     if ($comp) {
-       my $cmd;
-       if ($comp eq 'gzip') {
-           $cmd = ['zcat', $readfrom];
-       } elsif ($comp eq 'lzop') {
-           $cmd = ['lzop', '-d', '-c', $readfrom];
-       } else {
-           die "unknown compression method '$comp'\n";
-       }
+       my $info = PVE::Storage::decompressor_info('vma', $comp);
+       my $cmd = $info->{decompressor};
+       push @$cmd, $readfrom;
        $add_pipe->($cmd);
     }
 
@@ -6286,7 +6192,7 @@ sub restore_vma_archive {
 
        # we can read the config - that is already extracted
        my $fh = IO::File->new($cfgfn, "r") ||
-           "unable to read qemu-server.conf - $!\n";
+           die "unable to read qemu-server.conf - $!\n";
 
        my $fwcfgfn = "$tmpdir/qemu-server.fw";
        if (-f $fwcfgfn) {
@@ -7189,7 +7095,7 @@ sub complete_backup_archives {
     my $res = [];
     foreach my $id (keys %$data) {
        foreach my $item (@{$data->{$id}}) {
-           next if $item->{format} !~ m/^vma\.(gz|lzo)$/;
+           next if $item->{format} !~ m/^vma\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})$/;
            push @$res, $item->{volid} if defined($item->{volid});
        }
     }