]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
use vm_mon_cmd_nocheck at startup
[qemu-server.git] / PVE / QemuServer.pm
index 6b6a7e64e3bbdb39071933a380cd938db0d49d2a..165eaf6be6e5fe4b1c88454d28b113bc2b1f20af 100644 (file)
@@ -27,6 +27,7 @@ use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file
 use PVE::INotify;
 use PVE::ProcFSTools;
 use PVE::QMPClient;
+use PVE::RPCEnvironment;
 use Time::HiRes qw(gettimeofday);
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
@@ -205,8 +206,16 @@ my $confdesc = {
     balloon => {
         optional => 1,
         type => 'integer',
-        description => "Amount of target RAM for the VM in MB.",
-       minimum => 16,
+        description => "Amount of target RAM for the VM in MB. Using zero disables the ballon driver.",
+       minimum => 0,
+    },
+    shares => {
+        optional => 1,
+        type => 'integer',
+        description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning",
+       minimum => 0,
+       maximum => 50000,
+       default => 1000,
     },
     keyboard => {
        optional => 1,
@@ -235,7 +244,7 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 l24 l26)],
+        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26)],
        description => <<EODESC,
 Used to enable special optimization/features for specific
 operating systems:
@@ -247,11 +256,12 @@ w2k3   => Microsoft Windows 2003
 w2k8   => Microsoft Windows 2008
 wvista => Microsoft Windows Vista
 win7   => Microsoft Windows 7
+win8   => Microsoft Windows 8/2012
 l24    => Linux 2.4 Kernel
 l26    => Linux 2.6/3.X Kernel
 
-other|l24|l26                  ... no special behaviour
-wxp|w2k|w2k3|w2k8|wvista|win7  ... use --localtime switch
+other|l24|l26                       ... no special behaviour
+wxp|w2k|w2k3|w2k8|wvista|win7|win8  ... use --localtime switch
 EODESC
     },
     boot => {
@@ -309,8 +319,8 @@ EODESC
     tdf => {
        optional => 1,
        type => 'boolean',
-       description => "Enable/disable time drift fix. This is ignored for kvm versions newer that 1.0 (not needed anymore).",
-       default => 1,
+       description => "Enable/disable time drift fix.",
+       default => 0,
     },
     localtime => {
        optional => 1,
@@ -325,7 +335,7 @@ EODESC
     vga => {
        optional => 1,
        type => 'string',
-       description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win7/w2k8, and 'cirrur' for other OS types",
+       description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and 'cirrur' for other OS types",
        enum => [qw(std cirrus vmware)],
     },
     watchdog => {
@@ -387,13 +397,24 @@ EODESCR
        optional => 1,
        description => "Emulated CPU type.",
        type => 'string',
-       enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom cpu64-rhel6 cpu64-rhel5 Conroe Penryn Nehalem Westmere Opteron_G1 Opteron_G2 Opteron_G3 host) ],
+       enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge Haswell Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ],
        default => 'qemu64',
     },
     parent => get_standard_option('pve-snapshot-name', {
        optional => 1,
        description => "Parent snapshot name. This is used internally, and should not be modified.",
     }),
+    snaptime => {
+       optional => 1,
+       description => "Timestamp for snapshots.",
+       type => 'integer',
+       minimum => 0,
+    },
+    vmstate => {
+       optional => 1,
+       type => 'string', format => 'pve-volume-id',
+       description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
+    },
 };
 
 # what about other qemu settings ?
@@ -432,7 +453,6 @@ my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000',  'pcnet',  'virtio',
                      'ne2k_isa', 'i82551', 'i82557b', 'i82559er'];
 my $nic_model_list_txt = join(' ', sort @$nic_model_list);
 
-# fixme:
 my $netdesc = {
     optional => 1,
     type => 'string', format => 'pve-qm-net',
@@ -636,7 +656,7 @@ sub kvm_user_version {
 
     my $tmp = `kvm -help 2>/dev/null`;
 
-    if ($tmp =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?) /) {
+    if ($tmp =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?)[,\s]/) {
        $kvm_user_version = $2;
     }
 
@@ -679,6 +699,7 @@ sub os_list_description {
        w2k8 => 'Windows 2008',
        wvista => 'Windows Vista',
        win7 => 'Windows 7',
+       win8 => 'Windows 8/2012',
        l24 => 'Linux 2.4',
        l26 => 'Linux 2.6',
     };
@@ -1236,8 +1257,6 @@ sub add_unused_volume {
     return $key;
 }
 
-# fixme: remove all thos $noerr parameters?
-
 PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
 sub verify_bootdisk {
     my ($value, $noerr) = @_;
@@ -1393,6 +1412,7 @@ sub json_config_properties {
     my $prop = shift;
 
     foreach my $opt (keys %$confdesc) {
+       next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate';
        $prop->{$opt} = $confdesc->{$opt};
     }
 
@@ -1576,7 +1596,7 @@ sub parse_vm_config {
        if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
            my $snapname = $1;
            $conf->{description} = $descr if $descr;
-           my $descr = '';
+           $descr = '';
            $conf = $res->{snapshots}->{$snapname} = {}; 
            next;
        }
@@ -1942,6 +1962,11 @@ sub vmstatus {
        $d->{name} = $conf->{name} || "VM $vmid";
        $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0;
 
+       if ($conf->{balloon}) {
+           $d->{balloon_min} = $conf->{balloon}*(1024*1024);
+           $d->{shares} = $conf->{shares} || 1000;
+       }
+
        $d->{uptime} = 0;
        $d->{cpu} = 0;
        $d->{mem} = 0;
@@ -2015,6 +2040,25 @@ sub vmstatus {
 
     my $qmpclient = PVE::QMPClient->new();
 
+    my $ballooncb = sub {
+       my ($vmid, $resp) = @_;
+
+       my $info = $resp->{'return'};
+       return if !$info->{max_mem};
+       
+       my $d = $res->{$vmid};
+
+       # use memory assigned to VM
+       $d->{maxmem} = $info->{max_mem};
+       $d->{balloon} = $info->{actual};
+       
+       if (defined($info->{total_mem}) && defined($info->{free_mem})) {
+           $d->{mem} = $info->{total_mem} - $info->{free_mem};
+           $d->{freemem} = $info->{free_mem};
+       }
+
+    };
+
     my $blockstatscb = sub {
        my ($vmid, $resp) = @_;
        my $data = $resp->{'return'} || [];
@@ -2030,7 +2074,11 @@ sub vmstatus {
 
     my $statuscb = sub {
        my ($vmid, $resp) = @_;
+
        $qmpclient->queue_cmd($vmid, $blockstatscb, 'query-blockstats');
+       # this fails if ballon driver is not loaded, so this must be
+       # the last commnand (following command are aborted if this fails).
+       $qmpclient->queue_cmd($vmid, $ballooncb, 'query-balloon');
 
        my $status = 'unknown';
        if (!defined($status = $resp->{'return'}->{status})) {
@@ -2070,10 +2118,45 @@ sub foreach_drive {
     }
 }
 
+sub foreach_volid {
+    my ($conf, $func) = @_;
+    
+    my $volhash = {};
+
+    my $test_volid = sub {
+       my ($volid, $is_cdrom) = @_;
+
+       return if !$volid;
+       
+       $volhash->{$volid} = $is_cdrom || 0;
+    };
+
+    PVE::QemuServer::foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+       &$test_volid($drive->{file}, drive_is_cdrom($drive));
+    });
+
+    foreach my $snapname (keys %{$conf->{snapshots}}) {
+       my $snap = $conf->{snapshots}->{$snapname};
+       &$test_volid($snap->{vmstate}, 0);
+       PVE::QemuServer::foreach_drive($snap, sub {
+           my ($ds, $drive) = @_;
+           &$test_volid($drive->{file}, drive_is_cdrom($drive));
+        });
+    }
+
+    foreach my $volid (keys %$volhash) {
+       &$func($volid, $volhash->{$volid});     
+    }
+}
+
 sub config_to_command {
-    my ($storecfg, $vmid, $conf, $defaults, $migrate_uri) = @_;
+    my ($storecfg, $vmid, $conf, $defaults) = @_;
 
     my $cmd = [];
+    my $globalFlags = [];
+    my $machineFlags = [];
+    my $rtcFlags = [];
     my $devices = [];
     my $pciaddr = '';
     my $bridges = {};
@@ -2106,10 +2189,6 @@ sub config_to_command {
 
     push @$cmd, '-daemonize';
 
-    push @$cmd, '-incoming', $migrate_uri if $migrate_uri;
-
-    push @$cmd, '-S' if $migrate_uri;
-
     my $use_usb2 = 0;
     for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
        next if !$conf->{"usb$i"};
@@ -2198,7 +2277,7 @@ sub config_to_command {
 
     my $vga = $conf->{vga};
     if (!$vga) {
-       if ($conf->{ostype} && ($conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) {
+       if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || $conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) {
            $vga = 'std';
        } else {
            $vga = 'cirrus';
@@ -2209,43 +2288,42 @@ sub config_to_command {
 
     # time drift fix
     my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
-    # ignore - no longer supported by newer kvm
-    # push @$cmd, '-tdf' if $tdf;
 
     my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
+    my $useLocaltime = $conf->{localtime};
 
     if (my $ost = $conf->{ostype}) {
-       # other, wxp, w2k, w2k3, w2k8, wvista, win7, l24, l26
+       # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26
 
        if ($ost =~ m/^w/) { # windows
-           push @$cmd, '-localtime' if !defined($conf->{localtime});
+           $useLocaltime = 1 if !defined($conf->{localtime});
 
-           # use rtc-td-hack when acpi is enabled
+           # use time drift fix when acpi is enabled
            if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
-               push @$cmd, '-rtc-td-hack';
+               $tdf = 1 if !defined($conf->{tdf});
            }
        }
 
-       if ($ost eq 'win7' || $ost eq 'w2k8' || $ost eq 'wvista') {
-           push @$cmd, '-no-kvm-pit-reinjection';
+       if ($ost eq 'win7' || $ost eq 'win8' || $ost eq 'w2k8' || 
+           $ost eq 'wvista') {
+           push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
            push @$cmd, '-no-hpet';
        }
-
-       # -tdf ?
-       # -no-acpi
-       # -no-kvm
-       # -win2k-hack ?
     }
 
+    push @$rtcFlags, 'driftfix=slew' if $tdf;
+
     if ($nokvm) {
-       push @$cmd, '-no-kvm';
+       push @$machineFlags, 'accel=tcg';
     } else {
        die "No accelerator found!\n" if !$cpuinfo->{hvm};
     }
 
-    push @$cmd, '-localtime' if $conf->{localtime};
-
-    push @$cmd, '-startdate', $conf->{startdate} if $conf->{startdate};
+    if ($conf->{startdate}) {
+       push @$rtcFlags, "base=$conf->{startdate}";
+    } elsif ($useLocaltime) {
+       push @$rtcFlags, 'base=localtime';
+    }
 
     push @$cmd, '-S' if $conf->{freeze};
 
@@ -2266,8 +2344,11 @@ sub config_to_command {
        push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
     }
 
-    $pciaddr = print_pci_addr("balloon0", $bridges);
-    push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr" if $conf->{balloon};
+    # enable balloon by default, unless explicitly disabled
+    if (!defined($conf->{balloon}) || $conf->{balloon}) {
+       $pciaddr = print_pci_addr("balloon0", $bridges);
+       push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr";
+    }
 
     if ($conf->{watchdog}) {
        my $wdopts = parse_watchdog($conf->{watchdog});
@@ -2371,6 +2452,13 @@ sub config_to_command {
     }
 
     push @$cmd, @$devices;
+    push @$cmd, '-rtc', join(',', @$rtcFlags) 
+       if scalar(@$rtcFlags);
+    push @$cmd, '-machine', join(',', @$machineFlags) 
+       if scalar(@$machineFlags);
+    push @$cmd, '-global', join(',', @$globalFlags)
+       if scalar(@$globalFlags);
+
     return wantarray ? ($cmd, $vollist) : $cmd;
 }
 
@@ -2810,36 +2898,13 @@ sub qemu_volume_snapshot {
 sub qemu_volume_snapshot_delete {
     my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
 
-     #need to implement statefile location
-    my $statefile="/tmp/$vmid-$snap";
-
-    unlink $statefile if -e $statefile;
-
     my $running = PVE::QemuServer::check_running($vmid);
 
     return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
 
     return if !$running;
 
-    #need to split delvm monitor command like savevm
-
-}
-
-sub qemu_snapshot_start {
-    my ($vmid, $snap) = @_;
-
-    #need to implement statefile location
-    my $statefile="/tmp/$vmid-$snap";
-
-    vm_mon_cmd($vmid, "snapshot-start", statefile => $statefile);
-
-}
-
-sub qemu_snapshot_end {
-    my ($vmid) = @_;
-
-    vm_mon_cmd($vmid, "snapshot-end");
-
+    vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
 }
 
 sub qga_freezefs {
@@ -2855,7 +2920,7 @@ sub qga_unfreezefs {
 }
 
 sub vm_start {
-    my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom) = @_;
+    my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused) = @_;
 
     lock_config($vmid, sub {
        my $conf = load_config($vmid, $migratedfrom);
@@ -2864,28 +2929,28 @@ sub vm_start {
 
        die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
 
-       my $migrate_uri;
+       my $defaults = load_defaults();
+
+       # set environment variable useful inside network script
+       $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
+
+       my ($cmd, $vollist) = config_to_command($storecfg, $vmid, $conf, $defaults);
+
        my $migrate_port = 0;
 
        if ($statefile) {
            if ($statefile eq 'tcp') {
                $migrate_port = next_migrate_port();
-               $migrate_uri = "tcp:localhost:${migrate_port}";
+               my $migrate_uri = "tcp:localhost:${migrate_port}";
+               push @$cmd, '-incoming', $migrate_uri;
+               push @$cmd, '-S';
            } else {
-               if (-f $statefile) {
-                   $migrate_uri = "exec:cat $statefile";
-               } else {
-                   warn "state file '$statefile' does not exist - doing normal startup\n";
-               }
+               push @$cmd, '-loadstate', $statefile;
            }
+       } elsif ($paused) {
+           push @$cmd, '-S';
        }
 
-       my $defaults = load_defaults();
-
-       # set environment variable useful inside network script
-       $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
-
-       my ($cmd, $vollist) = config_to_command($storecfg, $vmid, $conf, $defaults, $migrate_uri);
        # host pci devices
         for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
           my $d = parse_hostpci($conf->{"hostpci$i"});
@@ -2899,19 +2964,16 @@ sub vm_start {
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       eval  { run_command($cmd, timeout => $migrate_uri ? undef : 30); };
+       eval  { run_command($cmd, timeout => $statefile ? undef : 30,
+                   umask => 0077); };
        my $err = $@;
        die "start failed: $err" if $err;
 
-       if ($statefile) {
+       print "migration listens on port $migrate_port\n" if $migrate_port;
 
-           if ($statefile eq 'tcp') {
-               print "migration listens on port $migrate_port\n";
-           } else {
-               unlink $statefile;
-               # fixme: send resume - is that necessary ?
-               eval { vm_mon_cmd($vmid, "cont"); };
-           }
+       if ($statefile && $statefile ne 'tcp')  {
+           eval { vm_mon_cmd_nocheck($vmid, "cont"); };
+           warn $@ if $@;
        }
 
        # always set migrate speed (overwrite kvm default of 32m)
@@ -2920,13 +2982,13 @@ sub vm_start {
        $migrate_speed = $conf->{migrate_speed} || $migrate_speed;
        $migrate_speed = $migrate_speed * 1048576;
        eval {
-           vm_mon_cmd($vmid, "migrate_set_speed", value => $migrate_speed);
+           vm_mon_cmd_nocheck($vmid, "migrate_set_speed", value => $migrate_speed);
        };
 
        my $migrate_downtime = $defaults->{migrate_downtime};
        $migrate_downtime = $conf->{migrate_downtime} if defined($conf->{migrate_downtime});
        if (defined($migrate_downtime)) {
-           eval { vm_mon_cmd($vmid, "migrate_set_downtime", value => $migrate_downtime); };
+           eval { vm_mon_cmd_nocheck($vmid, "migrate_set_downtime", value => $migrate_downtime); };
        }
 
        if($migratedfrom) {
@@ -2936,8 +2998,16 @@ sub vm_start {
            eval { PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
        }
 
-       vm_balloonset($vmid, $conf->{balloon}) if $conf->{balloon};
+       # fixme: how do we handle that on migration?
 
+       if (!defined($conf->{balloon}) || $conf->{balloon}) {
+           vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024) 
+               if $conf->{balloon};
+           vm_mon_cmd_nocheck($vmid, 'qom-set', 
+                      path => "machine/peripheral/balloon0", 
+                      property => "stats-polling-interval", 
+                      value => 2);
+       }
     });
 }
 
@@ -3031,14 +3101,13 @@ sub get_vm_volumes {
     my ($conf) = @_;
 
     my $vollist = [];
-    foreach_drive($conf, sub {
-       my ($ds, $drive) = @_;
+    foreach_volid($conf, sub {
+       my ($volid, $is_cdrom) = @_;
 
-       my ($sid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
-       return if !$sid;
+       return if $volid =~ m|^/|;
 
-       my $volid = $drive->{file};
-       return if !$volid || $volid =~ m|^/|;
+       my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+       return if !$sid;
 
        push @$vollist, $volid;
     });
@@ -3368,12 +3437,6 @@ sub print_pci_addr {
 
 }
 
-sub vm_balloonset {
-    my ($vmid, $value) = @_;
-
-    vm_mon_cmd($vmid, "balloon", value => $value);
-}
-
 # vzdump restore implementaion
 
 sub archive_read_firstfile {
@@ -3424,6 +3487,369 @@ sub restore_cleanup {
 sub restore_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
+    }
+
+    # try to detect archive format
+    if ($format eq 'tar') {
+       return restore_tar_archive($archive, $vmid, $user, $opts);
+    } else {
+       return restore_vma_archive($archive, $vmid, $user, $opts, $comp);
+    }
+}
+
+sub restore_update_config_line {
+    my ($outfd, $cookie, $vmid, $map, $line, $unique) = @_;
+
+    return if $line =~ m/^\#qmdump\#/;
+    return if $line =~ m/^\#vzdump\#/;
+    return if $line =~ m/^lock:/;
+    return if $line =~ m/^unused\d+:/;
+    return if $line =~ m/^parent:/;
+
+    if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
+       # try to convert old 1.X settings
+       my ($id, $ind, $ethcfg) = ($1, $2, $3);
+       foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {
+           my ($model, $macaddr) = split(/\=/, $devconfig);
+           $macaddr = PVE::Tools::random_ether_addr() if !$macaddr || $unique;
+           my $net = {
+               model => $model,
+               bridge => "vmbr$ind",
+               macaddr => $macaddr,
+           };
+           my $netstr = print_net($net);
+
+           print $outfd "net$cookie->{netcount}: $netstr\n";
+           $cookie->{netcount}++;
+       }
+    } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && $unique) {
+       my ($id, $netstr) = ($1, $2);
+       my $net = parse_net($netstr);
+       $net->{macaddr} = PVE::Tools::random_ether_addr() if $net->{macaddr};
+       $netstr = print_net($net);
+       print $outfd "$id: $netstr\n";
+    } elsif ($line =~ m/^((ide|scsi|virtio|sata)\d+):\s*(\S+)\s*$/) {
+       my $virtdev = $1;
+       my $value = $2;
+       if ($line =~ m/backup=no/) {
+           print $outfd "#$line";
+       } elsif ($virtdev && $map->{$virtdev}) {
+           my $di = PVE::QemuServer::parse_drive($virtdev, $value);
+           $di->{file} = $map->{$virtdev};
+           $value = PVE::QemuServer::print_drive($vmid, $di);
+           print $outfd "$virtdev: $value\n";
+       } else {
+           print $outfd $line;
+       }
+    } else {
+       print $outfd $line;
+    }
+}
+
+sub scan_volids {
+    my ($cfg, $vmid) = @_;
+
+    my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid);
+
+    my $volid_hash = {};
+    foreach my $storeid (keys %$info) {
+       foreach my $item (@{$info->{$storeid}}) {
+           next if !($item->{volid} && $item->{size});
+           $volid_hash->{$item->{volid}} = $item;
+       }
+    }
+
+    return $volid_hash;
+}
+
+sub update_disksize {
+    my ($vmid, $conf, $volid_hash) = @_;
+    my $changes;
+
+    my $used = {};
+
+    # update size info
+    foreach my $opt (keys %$conf) {
+       if (PVE::QemuServer::valid_drivename($opt)) {
+           my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+           my $volid = $drive->{file};
+           next if !$volid;
+
+           $used->{$volid} = 1;
+
+           next if PVE::QemuServer::drive_is_cdrom($drive);
+           next if !$volid_hash->{$volid};
+
+           $drive->{size} = $volid_hash->{$volid}->{size};
+           $changes = 1;
+           $conf->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
+       }
+    }
+
+    foreach my $volid (sort keys %$volid_hash) {
+       next if $volid =~ m/vm-$vmid-state-/;
+       next if $used->{$volid};
+       $changes = 1;
+       PVE::QemuServer::add_unused_volume($conf, $volid);
+    }
+
+    return $changes;
+}
+
+sub rescan {
+    my ($vmid, $nolock) = @_;
+
+    my $cfg = PVE::Cluster::cfs_read_file("storage.cfg");
+
+    my $volid_hash = scan_volids($cfg, $vmid);
+
+    my $updatefn =  sub {
+       my ($vmid) = @_;
+
+       my $conf = PVE::QemuServer::load_config($vmid);
+           
+       PVE::QemuServer::check_lock($conf);
+
+       my $changes = PVE::QemuServer::update_disksize($vmid, $conf, $volid_hash);
+
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1) if $changes;
+    };
+
+    if (defined($vmid)) {
+       if ($nolock) {
+           &$updatefn($vmid);
+       } else {
+           PVE::QemuServer::lock_config($vmid, $updatefn, $vmid);
+       }
+    } else {
+       my $vmlist = config_list();
+       foreach my $vmid (keys %$vmlist) {
+           if ($nolock) {
+               &$updatefn($vmid);
+           } else {
+               PVE::QemuServer::lock_config($vmid, $updatefn, $vmid);
+           }    
+       }
+    }
+}
+
+sub restore_vma_archive {
+    my ($archive, $vmid, $user, $opts, $comp) = @_;
+
+    my $input = $archive eq '-' ? "<&STDIN" : undef;
+    my $readfrom = $archive;
+
+    my $uncomp = '';
+    if ($comp) {
+       $readfrom = '-';
+       my $qarchive = PVE::Tools::shellquote($archive);
+       if ($comp eq 'gzip') {
+           $uncomp = "zcat $qarchive|";
+       } elsif ($comp eq 'lzop') {
+           $uncomp = "lzop -d -c $qarchive|";
+       } else {
+           die "unknown compression method '$comp'\n";
+       }
+       
+    }
+
+    my $tmpdir = "/var/tmp/vzdumptmp$$";
+    rmtree $tmpdir;
+
+    # disable interrupts (always do cleanups)
+    local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
+       warn "got interrupt - ignored\n";
+    };
+
+    my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
+    POSIX::mkfifo($mapfifo, 0600);
+    my $fifofh;
+
+    my $openfifo = sub {
+       open($fifofh, '>', $mapfifo) || die $!;
+    };
+
+    my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+
+    my $oldtimeout;
+    my $timeout = 5;
+
+    my $devinfo = {};
+
+    my $rpcenv = PVE::RPCEnvironment::get();
+
+    my $conffile = PVE::QemuServer::config_file($vmid);
+    my $tmpfn = "$conffile.$$.tmp";
+
+    my $print_devmap = sub {
+       my $virtdev_hash = {};
+
+       my $cfgfn = "$tmpdir/qemu-server.conf";
+
+       # we can read the config - that is already extracted
+       my $fh = IO::File->new($cfgfn, "r") ||
+           "unable to read qemu-server.conf - $!\n";
+
+       while (defined(my $line = <$fh>)) {
+           if ($line =~ m/^\#qmdump\#map:(\S+):(\S+):(\S*):(\S*):$/) {
+               my ($virtdev, $devname, $storeid, $format) = ($1, $2, $3, $4);
+               die "archive does not contain data for drive '$virtdev'\n"
+                   if !$devinfo->{$devname};
+               if (defined($opts->{storage})) {
+                   $storeid = $opts->{storage} || 'local';
+               } elsif (!$storeid) {
+                   $storeid = 'local';
+               }
+               $format = 'raw' if !$format;
+               $devinfo->{$devname}->{devname} = $devname;
+               $devinfo->{$devname}->{virtdev} = $virtdev;
+               $devinfo->{$devname}->{format} = $format;
+               $devinfo->{$devname}->{storeid} = $storeid;
+
+               # check permission on storage 
+               my $pool = $opts->{pool}; # todo: do we need that?
+               if ($user ne 'root@pam') {
+                   $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
+               }
+
+               $virtdev_hash->{$virtdev} = $devinfo->{$devname};
+           }
+       }
+
+       foreach my $devname (keys %$devinfo) {
+           die "found no device mapping information for device '$devname'\n" 
+               if !$devinfo->{$devname}->{virtdev};        
+       }
+
+       my $map = {};
+       my $cfg = cfs_read_file('storage.cfg');
+       foreach my $virtdev (sort keys %$virtdev_hash) {
+           my $d = $virtdev_hash->{$virtdev};
+           my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
+           my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+           my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
+                                                 $d->{format}, undef, $alloc_size);
+           print STDERR "new volume ID is '$volid'\n";
+           $d->{volid} = $volid;
+           my $path = PVE::Storage::path($cfg, $volid);
+
+           my $write_zeros = 1;
+           # fixme: what other storages types initialize volumes with zero?
+           if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+               $write_zeros = 0;
+           }
+
+           print $fifofh "${write_zeros}:$d->{devname}=$path\n";
+
+           print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
+           $map->{$virtdev} = $volid;
+       }
+
+       $fh->seek(0, 0) || die "seek failed - $!\n";
+
+       my $outfd = new IO::File ($tmpfn, "w") ||
+           die "unable to write config for VM $vmid\n";
+
+       my $cookie = { netcount => 0 };
+       while (defined(my $line = <$fh>)) {
+           restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});  
+       }
+
+       $fh->close();
+       $outfd->close();
+    };
+
+    eval {
+       # enable interrupts
+       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
+           die "interrupted by signal\n";
+       };
+       local $SIG{ALRM} = sub { die "got timeout\n"; };
+
+       $oldtimeout = alarm($timeout);
+
+       my $parser = sub {
+           my $line = shift;
+
+           print "$line\n";
+
+           if ($line =~ m/^DEV:\sdev_id=(\d+)\ssize:\s(\d+)\sdevname:\s(\S+)$/) {
+               my ($dev_id, $size, $devname) = ($1, $2, $3);
+               $devinfo->{$devname} = { size => $size, dev_id => $dev_id };
+           } elsif ($line =~ m/^CTIME: /) {
+               &$print_devmap();
+               print $fifofh "done\n";
+               my $tmp = $oldtimeout || 0;
+               $oldtimeout = undef;
+               alarm($tmp);
+               close($fifofh);
+           }
+       };
+       print "restore vma archive: $cmd\n";
+       run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+    };
+    my $err = $@;
+
+    alarm($oldtimeout) if $oldtimeout;
+
+    unlink $mapfifo;
+
+    if ($err) {
+       rmtree $tmpdir;
+       unlink $tmpfn;
+
+       my $cfg = cfs_read_file('storage.cfg');
+       foreach my $devname (keys %$devinfo) {
+           my $volid = $devinfo->{$devname}->{volid};
+           next if !$volid;
+           eval {
+               if ($volid =~ m|^/|) {
+                   unlink $volid || die 'unlink failed\n';
+               } else {
+                   PVE::Storage::vdisk_free($cfg, $volid);
+               }
+               print STDERR "temporary volume '$volid' sucessfuly removed\n";
+           };
+           print STDERR "unable to cleanup '$volid' - $@" if $@;
+       }
+       die $err;
+    }
+
+    rmtree $tmpdir;
+    
+    rename $tmpfn, $conffile ||
+       die "unable to commit configuration file '$conffile'\n";
+
+    eval { rescan($vmid, 1); };
+    warn $@ if $@;
+}
+
+sub restore_tar_archive {
+    my ($archive, $vmid, $user, $opts) = @_;
+
     if ($archive ne '-') {
        my $firstfile = archive_read_firstfile($archive);
        die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"
@@ -3495,50 +3921,9 @@ sub restore_archive {
        my $outfd = new IO::File ($tmpfn, "w") ||
            die "unable to write config for VM $vmid\n";
 
-       my $netcount = 0;
-
+       my $cookie = { netcount => 0 };
        while (defined (my $line = <$srcfd>)) {
-           next if $line =~ m/^\#vzdump\#/;
-           next if $line =~ m/^lock:/;
-           next if $line =~ m/^unused\d+:/;
-
-           if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
-               # try to convert old 1.X settings
-               my ($id, $ind, $ethcfg) = ($1, $2, $3);
-               foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {
-                   my ($model, $macaddr) = split(/\=/, $devconfig);
-                   $macaddr = PVE::Tools::random_ether_addr() if !$macaddr || $opts->{unique};
-                   my $net = {
-                       model => $model,
-                       bridge => "vmbr$ind",
-                       macaddr => $macaddr,
-                   };
-                   my $netstr = print_net($net);
-                   print $outfd "net${netcount}: $netstr\n";
-                   $netcount++;
-               }
-           } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && ($opts->{unique})) {
-               my ($id, $netstr) = ($1, $2);
-               my $net = parse_net($netstr);
-               $net->{macaddr} = PVE::Tools::random_ether_addr() if $net->{macaddr};
-               $netstr = print_net($net);
-               print $outfd "$id: $netstr\n";
-           } elsif ($line =~ m/^((ide|scsi|virtio)\d+):\s*(\S+)\s*$/) {
-               my $virtdev = $1;
-               my $value = $2;
-               if ($line =~ m/backup=no/) {
-                   print $outfd "#$line";
-               } elsif ($virtdev && $map->{$virtdev}) {
-                   my $di = PVE::QemuServer::parse_drive($virtdev, $value);
-                   $di->{file} = $map->{$virtdev};
-                   $value = PVE::QemuServer::print_drive($vmid, $di);
-                   print $outfd "$virtdev: $value\n";
-               } else {
-                   print $outfd $line;
-               }
-           } else {
-               print $outfd $line;
-           }
+           restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});  
        }
 
        $srcfd->close();
@@ -3559,6 +3944,9 @@ sub restore_archive {
 
     rename $tmpfn, $conffile ||
        die "unable to commit configuration file '$conffile'\n";
+
+    eval { rescan($vmid, 1); };
+    warn $@ if $@;
 };
 
 
@@ -3574,8 +3962,12 @@ my $snapshot_copy_config = sub {
 
     foreach my $k (keys %$source) {
        next if $k eq 'snapshots';
+       next if $k eq 'snapstate';
+       next if $k eq 'snaptime';
+       next if $k eq 'vmstate';
        next if $k eq 'lock';
        next if $k eq 'digest';
+       next if $k eq 'description';
        next if $k =~ m/^unused\d+$/;
                
        $dest->{$k} = $source->{$k};
@@ -3590,9 +3982,9 @@ my $snapshot_apply_config = sub {
        snapshots => $conf->{snapshots},
     };
 
-    # keep list of unused disks
+    # keep description and list of unused disks
     foreach my $k (keys %$conf) {
-       next if $k !~ m/^unused\d+$/;
+       next if !($k =~ m/^unused\d+$/ || $k eq 'description');
        $newconf->{$k} = $conf->{$k};
     }
 
@@ -3601,8 +3993,72 @@ my $snapshot_apply_config = sub {
     return $newconf;
 };
 
+sub foreach_writable_storage {
+    my ($conf, $func) = @_;
+
+    my $sidhash = {};
+
+    foreach my $ds (keys %$conf) {
+       next if !valid_drivename($ds);
+
+       my $drive = parse_drive($ds, $conf->{$ds});
+       next if !$drive;
+       next if drive_is_cdrom($drive);
+
+       my $volid = $drive->{file};
+
+       my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+       $sidhash->{$sid} = $sid if $sid;        
+    }
+
+    foreach my $sid (sort keys %$sidhash) {
+       &$func($sid);
+    }
+}
+
+my $alloc_vmstate_volid = sub {
+    my ($storecfg, $vmid, $conf, $snapname) = @_;
+    
+    # Note: we try to be smart when selecting a $target storage
+
+    my $target;
+
+    # search shared storage first
+    foreach_writable_storage($conf, sub {
+       my ($sid) = @_;
+       my $scfg = PVE::Storage::storage_config($storecfg, $sid);
+       return if !$scfg->{shared};
+
+       $target = $sid if !$target || $scfg->{path}; # prefer file based storage
+    });
+
+    if (!$target) {
+       # now search local storage
+       foreach_writable_storage($conf, sub {
+           my ($sid) = @_;
+           my $scfg = PVE::Storage::storage_config($storecfg, $sid);
+           return if $scfg->{shared};
+
+           $target = $sid if !$target || $scfg->{path}; # prefer file based storage;
+       });
+    }
+
+    $target = 'local' if !$target;
+
+    my $driver_state_size = 500; # assume 32MB is enough to safe all driver state;
+    # we abort live save after $conf->{memory}, so we need at max twice that space
+    my $size = $conf->{memory}*2 + $driver_state_size;
+
+    my $name = "vm-$vmid-state-$snapname";
+    my $scfg = PVE::Storage::storage_config($storecfg, $target);
+    $name .= ".raw" if $scfg->{path}; # add filename extension for file base storage
+    my $volid = PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size*1024);
+
+    return $volid;
+};
+
 my $snapshot_prepare = sub {
-    my ($vmid, $snapname) = @_;
+    my ($vmid, $snapname, $save_vmstate, $comment) = @_;
 
     my $snap;
 
@@ -3619,7 +4075,7 @@ my $snapshot_prepare = sub {
 
        my $storecfg = PVE::Storage::config();
 
-       PVE::QemuServer::foreach_drive($conf, sub {
+       foreach_drive($conf, sub {
            my ($ds, $drive) = @_;
 
            return if drive_is_cdrom($drive);
@@ -3630,6 +4086,7 @@ my $snapshot_prepare = sub {
                my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
                die "can't snapshot volume '$volid'\n"          
                    if !(($scfg->{path} && $volname =~ m/\.qcow2$/) ||
+                        ($scfg->{type} eq 'nexenta') || 
                         ($scfg->{type} eq 'rbd') || 
                         ($scfg->{type} eq 'sheepdog'));
            } elsif ($volid =~ m|^(/.+)$| && -e $volid) {
@@ -3639,12 +4096,19 @@ my $snapshot_prepare = sub {
            }
        });
 
-       $snap = $conf->{snapshots}->{$snapname} = {
-           snapstate => "prepare",
-       };
+
+       $snap = $conf->{snapshots}->{$snapname} = {};
+
+       if ($save_vmstate && check_running($vmid)) {
+           $snap->{vmstate} = &$alloc_vmstate_volid($storecfg, $vmid, $conf, $snapname);
+       }
 
        &$snapshot_copy_config($conf, $snap);
 
+       $snap->{snapstate} = "prepare";
+       $snap->{snaptime} = time();
+       $snap->{description} = $comment if $comment;
+
        update_config_nolock($vmid, $conf, 1);
     };
 
@@ -3675,6 +4139,8 @@ my $snapshot_commit = sub {
 
        my $newconf = &$snapshot_apply_config($conf, $snap);
 
+       $newconf->{parent} = $snapname;
+
        update_config_nolock($vmid, $newconf, 1);
     };
 
@@ -3688,11 +4154,23 @@ sub snapshot_rollback {
 
     my $prepare = 1;
 
+    my $storecfg = PVE::Storage::config();
     my $updatefn = sub {
 
        my $conf = load_config($vmid);
 
-       check_lock($conf) if $prepare;
+       $snap = $conf->{snapshots}->{$snapname};
+
+       die "snapshot '$snapname' does not exist\n" if !defined($snap); 
+
+       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" 
+           if $snap->{snapstate};
+
+       if ($prepare) {
+           check_lock($conf);
+           vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef);
+       }
 
        die "unable to rollback vm $vmid: vm is running\n"
            if check_running($vmid);
@@ -3704,13 +4182,6 @@ sub snapshot_rollback {
            delete $conf->{lock};
        }
 
-       $snap = $conf->{snapshots}->{$snapname};
-
-       die "snapshot '$snapname' does not exist\n" if !defined($snap); 
-
-       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" 
-           if $snap->{snapstate};
-
        if (!$prepare) {
            # copy snapshot config to current config
            $conf = &$snapshot_apply_config($conf, $snap);
@@ -3718,11 +4189,14 @@ sub snapshot_rollback {
        }
 
        update_config_nolock($vmid, $conf, 1);
+
+       if (!$prepare && $snap->{vmstate}) {
+           my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
+           vm_start($storecfg, $vmid, $statefile);
+       }
     };
 
     lock_config($vmid, $updatefn);
-
-    my $storecfg = PVE::Storage::config();
     
     foreach_drive($snap, sub {
        my ($ds, $drive) = @_;
@@ -3739,19 +4213,51 @@ sub snapshot_rollback {
     lock_config($vmid, $updatefn);
 }
 
+my $savevm_wait = sub {
+    my ($vmid) = @_;
+
+    for(;;) {
+       my $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-savevm");
+       if (!$stat->{status}) {
+           die "savevm not active\n";
+       } elsif ($stat->{status} eq 'active') {
+           sleep(1);
+           next;
+       } elsif ($stat->{status} eq 'completed') {
+           last;
+       } else {
+           die "query-savevm returned status '$stat->{status}'\n";
+       }
+    }
+};
+
 sub snapshot_create {
-    my ($vmid, $snapname, $vmstate, $freezefs) = @_;
+    my ($vmid, $snapname, $save_vmstate, $freezefs, $comment) = @_;
+
+    my $snap = &$snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
+
+    $freezefs = $save_vmstate = 0 if !$snap->{vmstate}; # vm is not running
 
-    my $snap = &$snapshot_prepare($vmid, $snapname);
+    my $drivehash = {};
+
+    my $running = check_running($vmid);
 
     eval {
        # create internal snapshots of all drives
-       
-       qemu_snapshot_start($vmid, $snapname) if $vmstate;
-
-       qga_freezefs($vmid) if $freezefs;
 
        my $storecfg = PVE::Storage::config();
+
+       if ($running) {
+           if ($snap->{vmstate}) {
+               my $path = PVE::Storage::path($storecfg, $snap->{vmstate});     
+               vm_mon_cmd($vmid, "savevm-start", statefile => $path);
+               &$savevm_wait($vmid);
+           } else {
+               vm_mon_cmd($vmid, "savevm-start");
+           }
+       };
+
+       qga_freezefs($vmid) if $running && $freezefs;
  
        foreach_drive($snap, sub {
            my ($ds, $drive) = @_;
@@ -3762,19 +4268,20 @@ sub snapshot_create {
            my $device = "drive-$ds";
 
            qemu_volume_snapshot($vmid, $device, $storecfg, $volid, $snapname);
+           $drivehash->{$ds} = 1;
        });
     };
     my $err = $@;
 
-    eval { gqa_unfreezefs($vmid) if $freezefs; };
+    eval { gqa_unfreezefs($vmid) if $running && $freezefs; };
     warn $@ if $@;
 
-    eval { qemu_snapshot_end($vmid) if $vmstate; };
+    eval { vm_mon_cmd($vmid, "savevm-end") if $running; };
     warn $@ if $@;
 
     if ($err) {
        warn "snapshot create failed: starting cleanup\n";
-       eval { snapshot_delete($vmid, $snapname, 1); };
+       eval { snapshot_delete($vmid, $snapname, 0, $drivehash); };
        warn $@ if $@;
        die $err;
     }
@@ -3782,42 +4289,61 @@ sub snapshot_create {
     &$snapshot_commit($vmid, $snapname);
 }
 
+# Note: $drivehash is only set when called from snapshot_create.
 sub snapshot_delete {
-    my ($vmid, $snapname, $force) = @_;
+    my ($vmid, $snapname, $force, $drivehash) = @_;
 
     my $prepare = 1;
 
     my $snap;
     my $unused = [];
 
+    my $unlink_parent = sub {
+       my ($confref, $new_parent) = @_;
+
+       if ($confref->{parent} && $confref->{parent} eq $snapname) {
+           if ($new_parent) {
+               $confref->{parent} = $new_parent;
+           } else {
+               delete $confref->{parent};
+           }
+       }
+    };
     my $updatefn =  sub {
+       my ($remove_drive) = @_;
 
        my $conf = load_config($vmid);
 
-       check_lock($conf) if !$force;
+       check_lock($conf) if !$drivehash;
 
        $snap = $conf->{snapshots}->{$snapname};
 
        die "snapshot '$snapname' does not exist\n" if !defined($snap); 
 
        # remove parent refs
+       &$unlink_parent($conf, $snap->{parent});
        foreach my $sn (keys %{$conf->{snapshots}}) {
            next if $sn eq $snapname;
-           my $snapref = $conf->{snapshots}->{$sn};
-           if ($snapref->{parent} && $snapref->{parent} eq $snapname) {
-               if ($snap->{parent}) {
-                   $snapref->{parent} = $snap->{parent};
-               } else {
-                   delete $snapref->{parent};
-               }
+           &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
+       }
+
+       if ($remove_drive) {
+           if ($remove_drive eq 'vmstate') {
+               delete $snap->{$remove_drive};
+           } else {
+               my $drive = parse_drive($remove_drive, $snap->{$remove_drive});
+               my $volid = $drive->{file};
+               delete $snap->{$remove_drive};
+               add_unused_volume($conf, $volid);
            }
        }
 
        if ($prepare) {
            $snap->{snapstate} = 'delete';
        } else {
-           delete $conf->{parent} if $conf->{parent} && $conf->{parent} eq $snapname;
            delete $conf->{snapshots}->{$snapname};
+           delete $conf->{lock} if $drivehash;
            foreach my $volid (@$unused) {
                add_unused_volume($conf, $volid);
            }
@@ -3828,18 +4354,39 @@ sub snapshot_delete {
 
     lock_config($vmid, $updatefn);
 
-    # now remove all internal snapshots
+    # now remove vmstate file
 
     my $storecfg = PVE::Storage::config();
 
-    PVE::QemuServer::foreach_drive($snap, sub {
+    if ($snap->{vmstate}) {
+       eval {  PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); };
+       if (my $err = $@) {
+           die $err if !$force;
+           warn $err;
+       }
+       # save changes (remove vmstate from snapshot)
+       lock_config($vmid, $updatefn, 'vmstate') if !$force;
+    };
+
+    # now remove all internal snapshots
+    foreach_drive($snap, sub {
        my ($ds, $drive) = @_;
 
        return if drive_is_cdrom($drive);
+
        my $volid = $drive->{file};
        my $device = "drive-$ds";
 
-       qemu_volume_snapshot_delete($vmid, $device, $storecfg, $volid, $snapname);
+       if (!$drivehash || $drivehash->{$ds}) {
+           eval { qemu_volume_snapshot_delete($vmid, $device, $storecfg, $volid, $snapname); };
+           if (my $err = $@) {
+               die $err if !$force;
+               warn $err;
+           }
+       }
+
+       # save changes (remove drive fron snapshot)
+       lock_config($vmid, $updatefn, $ds) if !$force;
        push @$unused, $volid;
     });