]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix pbs-restore call for non raw images
[qemu-server.git] / PVE / QemuServer.pm
index 092d9289578c674e221ed93634e2fcbfeced7e7e..c262f4aa1cc47583e1f6dad020d2786b6bdedb98 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;
@@ -2751,6 +2751,32 @@ sub conf_has_audio {
     };
 }
 
+sub audio_devs {
+    my ($audio, $audiopciaddr, $machine_version) = @_;
+
+    my $devs = [];
+
+    my $id = $audio->{dev_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";
+    } 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";
+    } else {
+       die "unkown audio device '$audio->{dev}', implement me!";
+    }
+
+    push @$devs, '-audiodev', "$audio->{backend},id=$audio->{backend_id}";
+
+    return $devs;
+}
+
 sub vga_conf_has_spice {
     my ($vga) = @_;
 
@@ -3056,12 +3082,11 @@ sub config_to_command {
        }
     }
 
-    my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch);
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-       die "uefi base image not found\n" if ! -f $ovmf_code;
+       my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch);
+       die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code;
 
-       my $path;
-       my $format;
+       my ($path, $format);
        if (my $efidisk = $conf->{efidisk0}) {
            my $d = parse_drive('efidisk0', $efidisk);
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
@@ -3252,22 +3277,10 @@ sub config_to_command {
        }
     }
 
-    if (my $audio = conf_has_audio($conf)) {
-
+    if (min_version($machine_version, 4, 0) && (my $audio = conf_has_audio($conf))) {
        my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type);
-
-       my $id = $audio->{dev_id};
-       if ($audio->{dev} eq 'AC97') {
-           push @$devices, '-device', "AC97,id=${id}${audiopciaddr}";
-       } elsif ($audio->{dev} =~ /intel\-hda$/) {
-           push @$devices, '-device', "$audio->{dev},id=${id}${audiopciaddr}";
-           push @$devices, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0";
-           push @$devices, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1";
-       } else {
-           die "unkown audio device '$audio->{dev}', implement me!";
-       }
-
-       push @$devices, '-audiodev', "$audio->{backend},id=$audio->{backend_id}";
+       my $audio_devs = audio_devs($audio, $audiopciaddr, $machine_version);
+       push @$devices, @$audio_devs;
     }
 
     my $sockets = 1;
@@ -3378,20 +3391,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";
     }
@@ -3625,6 +3634,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) = @_;
 
@@ -4038,6 +4065,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;
 }
@@ -4294,40 +4329,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) {
@@ -5370,7 +5411,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 {
@@ -5618,28 +5663,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') {
@@ -5730,12 +5759,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,
                };
@@ -6146,6 +6176,8 @@ sub restore_proxmox_backup_archive {
                '--verbose',
                ];
 
+           push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+
            if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
                push @$pbs_restore_cmd, '--skip-zero';
            }
@@ -6226,14 +6258,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);
     }
 
@@ -7180,7 +7207,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});
        }
     }