]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
cfg: use the 'urlencoded' format for drive model and serial
[qemu-server.git] / PVE / QemuServer.pm
index 17b43d2a90144ac7c88a9dda28feabad646c0877..0d0b65bf1f738bb32d700ada75ff5e8691648bcf 100644 (file)
@@ -27,10 +27,12 @@ use PVE::JSONSchema qw(get_standard_option);
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
 use PVE::INotify;
 use PVE::ProcFSTools;
+use PVE::QemuConfig;
 use PVE::QMPClient;
 use PVE::RPCEnvironment;
 use Time::HiRes qw(gettimeofday);
 use File::Copy qw(copy);
+use URI::Escape;
 
 my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
 
@@ -259,7 +261,7 @@ EODESC
     numa => {
        optional => 1,
        type => 'boolean',
-       description => "Enable/disable Numa.",
+       description => "Enable/disable NUMA.",
        default => 0,
     },
     vcpus => {
@@ -306,14 +308,24 @@ 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 win8/win7/w2k8, and 'cirrur' for other OS types. Option 'qxl' enables the SPICE display sever. You can also run without any graphic card using a serial devive as terminal.",
+       description => "Select the VGA type. If you want to use high resolution" .
+           " modes (>= 1280x1024x16) then you should use the options " .
+           "'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and " .
+           "'cirrus' for other OS types. The 'qxl' option enables the SPICE " .
+           "display sever. For win* OS you can select how many independent " .
+           "displays you want, Linux guests can add displays them self. " .
+           "You can also run without any graphic card, using a serial device" .
+           " as terminal.",
        enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
     },
     watchdog => {
        optional => 1,
        type => 'string', format => 'pve-qm-watchdog',
        typetext => '[[model=]i6300esb|ib700] [,[action=]reset|shutdown|poweroff|pause|debug|none]',
-       description => "Create a virtual hardware watchdog device.  Once enabled (by a guest action), the watchdog must be periodically polled by an agent inside the guest or else the guest will be restarted (or execute the action specified)",
+       description => "Create a virtual hardware watchdog device. Once enabled" .
+           " (by a guest action), the watchdog must be periodically polled " .
+           "by an agent inside the guest or else the watchdog will reset " .
+           "the guest (or execute the respective action specified)",
     },
     startdate => {
        optional => 1,
@@ -343,7 +355,12 @@ EODESCR
        optional => 1,
        type => 'boolean',
        default => 1,
-       description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning with VNC. Else the mouse runs out of sync with normal VNC clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches. This is turned of by default if you use spice (vga=qxl).",
+       description => "Enable/disable the USB tablet device. This device is " .
+           "usually needed to allow absolute mouse positioning with VNC. " .
+           "Else the mouse runs out of sync with normal VNC clients. " .
+           "If you're running lots of console-only guests on one host, " .
+           "you may consider disabling this to save some context switches. " .
+           "This is turned off by default if you use spice (-vga=qxl).",
     },
     migrate_speed => {
        optional => 1,
@@ -601,8 +618,9 @@ my %drivedesc_base = (
     },
     serial => {
        type => 'string',
+       format => 'urlencoded',
        format_description => 'serial',
-       description => "The drive's reported serial number.",
+       description => "The drive's reported serial number, url-encoded.",
        optional => 1,
     }
 );
@@ -627,8 +645,9 @@ my %iothread_fmt = ( iothread => {
 my %model_fmt = (
     model => {
        type => 'string',
+       format => 'urlencoded',
        format_description => 'model',
-       description => "The drive's reported model name.",
+       description => "The drive's reported model name, url-encoded.",
        optional => 1,
     },
 );
@@ -1246,6 +1265,7 @@ sub print_drivedevice_full {
 
        $device = "ide-$devicetype,bus=ide.$controller,unit=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
        if ($devicetype eq 'hd' && (my $model = $drive->{model})) {
+           $model = URI::Escape::uri_unescape($model);
            $device .= ",model=$model";
        }
     } elsif ($drive->{interface} eq 'sata'){
@@ -1278,7 +1298,6 @@ sub get_initiator_name {
     return $initiator;
 }
 
-my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max serial);
 sub print_drive_full {
     my ($storecfg, $vmid, $drive) = @_;
 
@@ -1301,10 +1320,14 @@ sub print_drive_full {
    }
 
     my $opts = '';
+    my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max);
     foreach my $o (@qemu_drive_options) {
-       next if $o eq 'bootindex';
        $opts .= ",$o=$drive->{$o}" if $drive->{$o};
     }
+    if (my $serial = $drive->{serial}) {
+       $serial = URI::Escape::uri_unescape($serial);
+       $opts .= ",serial=$serial";
+    }
 
     $opts .= ",format=$format" if $format && !$drive->{format};
 
@@ -1556,26 +1579,6 @@ sub add_random_macs {
     }
 }
 
-sub add_unused_volume {
-    my ($config, $volid) = @_;
-
-    my $key;
-    for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
-       my $test = "unused$ind";
-       if (my $vid = $config->{$test}) {
-           return if $vid eq $volid; # do not add duplicates
-       } else {
-           $key = $test;
-       }
-    }
-
-    die "To many unused volume - please delete them first.\n" if !$key;
-
-    $config->{$key} = $volid;
-
-    return $key;
-}
-
 sub vm_is_volid_owner {
     my ($storecfg, $vmid, $volid) = @_;
 
@@ -1630,7 +1633,7 @@ sub vmconfig_register_unused_drive {
     if (!drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
        if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
-           add_unused_volume($conf, $volid, $vmid);
+           PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);
        }
     }
 }
@@ -1891,43 +1894,6 @@ sub check_type {
     }
 }
 
-sub lock_config_full {
-    my ($vmid, $timeout, $code, @param) = @_;
-
-    my $filename = config_file_lock($vmid);
-
-    my $res = lock_file($filename, $timeout, $code, @param);
-
-    die $@ if $@;
-
-    return $res;
-}
-
-sub lock_config_mode {
-    my ($vmid, $timeout, $shared, $code, @param) = @_;
-
-    my $filename = config_file_lock($vmid);
-
-    my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
-
-    die $@ if $@;
-
-    return $res;
-}
-
-sub lock_config {
-    my ($vmid, $code, @param) = @_;
-
-    return lock_config_full($vmid, 10, $code, @param);
-}
-
-sub cfs_config_path {
-    my ($vmid, $node) = @_;
-
-    $node = $nodename if !$node;
-    return "nodes/$node/qemu-server/$vmid.conf";
-}
-
 sub check_iommu_support{
     #fixme : need to check IOMMU support
     #http://www.linux-kvm.org/page/How_to_assign_devices_with_VT-d_in_KVM
@@ -1937,34 +1903,21 @@ sub check_iommu_support{
 
 }
 
-sub config_file {
-    my ($vmid, $node) = @_;
-
-    my $cfspath = cfs_config_path($vmid, $node);
-    return "/etc/pve/$cfspath";
-}
-
-sub config_file_lock {
-    my ($vmid) = @_;
-
-    return "$lock_dir/lock-$vmid.conf";
-}
-
 sub touch_config {
     my ($vmid) = @_;
 
-    my $conf = config_file($vmid);
+    my $conf = PVE::QemuConfig->config_file($vmid);
     utime undef, undef, $conf;
 }
 
 sub destroy_vm {
     my ($storecfg, $vmid, $keep_empty_config, $skiplock) = @_;
 
-    my $conffile = config_file($vmid);
+    my $conffile = PVE::QemuConfig->config_file($vmid);
 
-    my $conf = load_config($vmid);
+    my $conf = PVE::QemuConfig->load_config($vmid);
 
-    check_lock($conf) if !$skiplock;
+    PVE::QemuConfig->check_lock($conf) if !$skiplock;
 
     # only remove disks owned by this VM
     foreach_drive($conf, sub {
@@ -2004,18 +1957,6 @@ sub destroy_vm {
     warn $@ if $@;
 }
 
-sub load_config {
-    my ($vmid, $node) = @_;
-
-    my $cfspath = cfs_config_path($vmid, $node);
-
-    my $conf = PVE::Cluster::cfs_read_file($cfspath);
-
-    die "no such VM ('$vmid')\n" if !defined($conf);
-
-    return $conf;
-}
-
 sub parse_vm_config {
     my ($filename, $raw) = @_;
 
@@ -2222,14 +2163,6 @@ sub write_vm_config {
     return $raw;
 }
 
-sub write_config {
-    my ($vmid, $conf) = @_;
-
-    my $cfspath = cfs_config_path($vmid);
-
-    PVE::Cluster::cfs_write_file($cfspath, $conf);
-}
-
 sub load_defaults {
 
     my $res = {};
@@ -2336,12 +2269,6 @@ sub shared_nodes {
     return $nodehash
 }
 
-sub check_lock {
-    my ($conf) = @_;
-
-    die "VM is locked ($conf->{lock})\n" if $conf->{lock};
-}
-
 sub check_cmdline {
     my ($pidfile, $pid) = @_;
 
@@ -2371,7 +2298,7 @@ sub check_cmdline {
 sub check_running {
     my ($vmid, $nocheck, $node) = @_;
 
-    my $filename = config_file($vmid, $node);
+    my $filename = PVE::QemuConfig->config_file($vmid, $node);
 
     die "unable to find configuration file for VM $vmid - no such machine\n"
        if !$nocheck && ! -f $filename;
@@ -2459,7 +2386,7 @@ sub vmstatus {
     foreach my $vmid (keys %$list) {
        next if $opt_vmid && ($vmid ne $opt_vmid);
 
-       my $cfspath = cfs_config_path($vmid);
+       my $cfspath = PVE::QemuConfig->cfs_config_path($vmid);
        my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
 
        my $d = {};
@@ -2499,7 +2426,7 @@ sub vmstatus {
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
 
-        $d->{template} = is_template($conf);
+        $d->{template} = PVE::QemuConfig->is_template($conf);
 
        $res->{$vmid} = $d;
     }
@@ -3196,7 +3123,7 @@ sub config_to_command {
            #if dimm_memory is not aligned to dimm map
            if($current_size > $memory) {
                 $conf->{memory} = $current_size;
-                write_config($vmid, $conf);
+                PVE::QemuConfig->write_config($vmid, $conf);
            }
        });
     }
@@ -3853,7 +3780,7 @@ sub qemu_memory_hotplug {
                }
                #update conf after each succesful module hotplug
                $conf->{memory} = $current_size;
-               write_config($vmid, $conf);
+               PVE::QemuConfig->write_config($vmid, $conf);
        });
 
     } else {
@@ -3878,7 +3805,7 @@ sub qemu_memory_hotplug {
                $conf->{memory} = $current_size;
 
                eval { qemu_objectdel($vmid, "mem-$name"); };
-               write_config($vmid, $conf);
+               PVE::QemuConfig->write_config($vmid, $conf);
        });
     }
 }
@@ -4131,8 +4058,8 @@ sub vmconfig_hotplug_pending {
     }
 
     if ($changes) {
-       write_config($vmid, $conf);
-       $conf = load_config($vmid); # update/reload
+       PVE::QemuConfig->write_config($vmid, $conf);
+       $conf = PVE::QemuConfig->load_config($vmid); # update/reload
     }
 
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
@@ -4182,8 +4109,8 @@ sub vmconfig_hotplug_pending {
            # save new config if hotplug was successful
            delete $conf->{$opt};
            vmconfig_undelete_pending_option($conf, $opt);
-           write_config($vmid, $conf);
-           $conf = load_config($vmid); # update/reload
+           PVE::QemuConfig->write_config($vmid, $conf);
+           $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        }
     }
 
@@ -4240,8 +4167,8 @@ sub vmconfig_hotplug_pending {
            # save new config if hotplug was successful
            $conf->{$opt} = $value;
            delete $conf->{pending}->{$opt};
-           write_config($vmid, $conf);
-           $conf = load_config($vmid); # update/reload
+           PVE::QemuConfig->write_config($vmid, $conf);
+           $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        }
     }
 }
@@ -4293,26 +4220,26 @@ sub vmconfig_apply_pending {
     my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
     while (my ($opt, $force) = each %$pending_delete_hash) {
        die "internal error" if $opt =~ m/^unused/;
-       $conf = load_config($vmid); # update/reload
+       $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        if (!defined($conf->{$opt})) {
            vmconfig_undelete_pending_option($conf, $opt);
-           write_config($vmid, $conf);
+           PVE::QemuConfig->write_config($vmid, $conf);
        } elsif (is_valid_drivename($opt)) {
            vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
            vmconfig_undelete_pending_option($conf, $opt);
            delete $conf->{$opt};
-           write_config($vmid, $conf);
+           PVE::QemuConfig->write_config($vmid, $conf);
        } else {
            vmconfig_undelete_pending_option($conf, $opt);
            delete $conf->{$opt};
-           write_config($vmid, $conf);
+           PVE::QemuConfig->write_config($vmid, $conf);
        }
     }
 
-    $conf = load_config($vmid); # update/reload
+    $conf = PVE::QemuConfig->load_config($vmid); # update/reload
 
     foreach my $opt (keys %{$conf->{pending}}) { # add/change
-       $conf = load_config($vmid); # update/reload
+       $conf = PVE::QemuConfig->load_config($vmid); # update/reload
 
        if (defined($conf->{$opt}) && ($conf->{$opt} eq $conf->{pending}->{$opt})) {
            # skip if nothing changed
@@ -4325,7 +4252,7 @@ sub vmconfig_apply_pending {
        }
 
        delete $conf->{pending}->{$opt};
-       write_config($vmid, $conf);
+       PVE::QemuConfig->write_config($vmid, $conf);
     }
 }
 
@@ -4370,16 +4297,16 @@ sub vmconfig_update_net {
            die "internal error" if $opt !~ m/net(\d+)/;
            my $iface = "tap${vmid}i$1";
 
-           if (&$safe_num_ne($oldnet->{rate}, $newnet->{rate})) {
-               PVE::Network::tap_rate_limit($iface, $newnet->{rate});
-           }
-
            if (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
                &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
                &$safe_string_ne($oldnet->{trunks}, $newnet->{trunks}) ||
                &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
                PVE::Network::tap_unplug($iface);
-               PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
+               PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
+           } elsif (&$safe_num_ne($oldnet->{rate}, $newnet->{rate})) {
+               # Rate can be applied on its own but any change above needs to
+               # include the rate in tap_plug since OVS resets everything.
+               PVE::Network::tap_rate_limit($iface, $newnet->{rate});
            }
 
            if (&$safe_string_ne($oldnet->{link_down}, $newnet->{link_down})) {
@@ -4491,18 +4418,18 @@ sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
        $forcemachine, $spice_ticket) = @_;
 
-    lock_config($vmid, sub {
-       my $conf = load_config($vmid, $migratedfrom);
+    PVE::QemuConfig->lock_config($vmid, sub {
+       my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
 
-       die "you can't start a vm if it's a template\n" if is_template($conf);
+       die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
 
-       check_lock($conf) if !$skiplock;
+       PVE::QemuConfig->check_lock($conf) if !$skiplock;
 
        die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
 
        if (!$statefile && scalar(keys %{$conf->{pending}})) {
            vmconfig_apply_pending($vmid, $conf, $storecfg);
-           $conf = load_config($vmid); # update/reload
+           $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        }
 
        my $defaults = load_defaults();
@@ -4670,7 +4597,7 @@ sub vm_human_monitor_command {
 sub vm_commandline {
     my ($storecfg, $vmid) = @_;
 
-    my $conf = load_config($vmid);
+    my $conf = PVE::QemuConfig->load_config($vmid);
 
     my $defaults = load_defaults();
 
@@ -4682,11 +4609,11 @@ sub vm_commandline {
 sub vm_reset {
     my ($vmid, $skiplock) = @_;
 
-    lock_config($vmid, sub {
+    PVE::QemuConfig->lock_config($vmid, sub {
 
-       my $conf = load_config($vmid);
+       my $conf = PVE::QemuConfig->load_config($vmid);
 
-       check_lock($conf) if !$skiplock;
+       PVE::QemuConfig->check_lock($conf) if !$skiplock;
 
        vm_mon_cmd($vmid, "system_reset");
     });
@@ -4740,20 +4667,20 @@ sub vm_stop {
     if ($migratedfrom){
        my $pid = check_running($vmid, $nocheck, $migratedfrom);
        kill 15, $pid if $pid;
-       my $conf = load_config($vmid, $migratedfrom);
+       my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
        vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 0);
        return;
     }
 
-    lock_config($vmid, sub {
+    PVE::QemuConfig->lock_config($vmid, sub {
 
        my $pid = check_running($vmid, $nocheck);
        return if !$pid;
 
        my $conf;
        if (!$nocheck) {
-           $conf = load_config($vmid);
-           check_lock($conf) if !$skiplock;
+           $conf = PVE::QemuConfig->load_config($vmid);
+           PVE::QemuConfig->check_lock($conf) if !$skiplock;
            if (!defined($timeout) && $shutdown && $conf->{startup}) {
                my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
                $timeout = $opts->{down} if $opts->{down};
@@ -4824,11 +4751,12 @@ sub vm_stop {
 sub vm_suspend {
     my ($vmid, $skiplock) = @_;
 
-    lock_config($vmid, sub {
+    PVE::QemuConfig->lock_config($vmid, sub {
 
-       my $conf = load_config($vmid);
+       my $conf = PVE::QemuConfig->load_config($vmid);
 
-       check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
+       PVE::QemuConfig->check_lock($conf)
+           if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
 
        vm_mon_cmd($vmid, "stop");
     });
@@ -4837,13 +4765,14 @@ sub vm_suspend {
 sub vm_resume {
     my ($vmid, $skiplock, $nocheck) = @_;
 
-    lock_config($vmid, sub {
+    PVE::QemuConfig->lock_config($vmid, sub {
 
        if (!$nocheck) {
 
-           my $conf = load_config($vmid);
+           my $conf = PVE::QemuConfig->load_config($vmid);
 
-           check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
+           PVE::QemuConfig->check_lock($conf)
+               if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
 
            vm_mon_cmd($vmid, "cont");
 
@@ -4856,9 +4785,9 @@ sub vm_resume {
 sub vm_sendkey {
     my ($vmid, $skiplock, $key) = @_;
 
-    lock_config($vmid, sub {
+    PVE::QemuConfig->lock_config($vmid, sub {
 
-       my $conf = load_config($vmid);
+       my $conf = PVE::QemuConfig->load_config($vmid);
 
        # there is no qmp command, so we use the human monitor command
        vm_human_monitor_command($vmid, "sendkey $key");
@@ -4868,9 +4797,9 @@ sub vm_sendkey {
 sub vm_destroy {
     my ($storecfg, $vmid, $skiplock) = @_;
 
-    lock_config($vmid, sub {
+    PVE::QemuConfig->lock_config($vmid, sub {
 
-       my $conf = load_config($vmid);
+       my $conf = PVE::QemuConfig->load_config($vmid);
 
        if (!check_running($vmid)) {
            destroy_vm($storecfg, $vmid, undef, $skiplock);
@@ -5380,7 +5309,7 @@ sub update_disksize {
        next if !$path; # just to be sure
        next if $usedpath->{$path};
        $changes = 1;
-       add_unused_volume($conf, $volid);
+       PVE::QemuConfig->add_unused_volume($conf, $volid);
        $usedpath->{$path} = 1; # avoid to add more than once (aliases)
     }
 
@@ -5397,9 +5326,9 @@ sub rescan {
     my $updatefn =  sub {
        my ($vmid) = @_;
 
-       my $conf = load_config($vmid);
+       my $conf = PVE::QemuConfig->load_config($vmid);
 
-       check_lock($conf);
+       PVE::QemuConfig->check_lock($conf);
 
        my $vm_volids = {};
        foreach my $volid (keys %$volid_hash) {
@@ -5409,14 +5338,14 @@ sub rescan {
 
        my $changes = update_disksize($vmid, $conf, $vm_volids);
 
-       write_config($vmid, $conf) if $changes;
+       PVE::QemuConfig->write_config($vmid, $conf) if $changes;
     };
 
     if (defined($vmid)) {
        if ($nolock) {
            &$updatefn($vmid);
        } else {
-           lock_config($vmid, $updatefn, $vmid);
+           PVE::QemuConfig->lock_config($vmid, $updatefn, $vmid);
        }
     } else {
        my $vmlist = config_list();
@@ -5424,7 +5353,7 @@ sub rescan {
            if ($nolock) {
                &$updatefn($vmid);
            } else {
-               lock_config($vmid, $updatefn, $vmid);
+               PVE::QemuConfig->lock_config($vmid, $updatefn, $vmid);
            }
        }
     }
@@ -5475,11 +5404,12 @@ sub restore_vma_archive {
 
     my $rpcenv = PVE::RPCEnvironment::get();
 
-    my $conffile = config_file($vmid);
+    my $conffile = PVE::QemuConfig->config_file($vmid);
     my $tmpfn = "$conffile.$$.tmp";
 
     # Note: $oldconf is undef if VM does not exists
-    my $oldconf = PVE::Cluster::cfs_read_file(cfs_config_path($vmid));
+    my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
+    my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
 
     my $print_devmap = sub {
        my $virtdev_hash = {};
@@ -5701,7 +5631,7 @@ sub restore_tar_archive {
     my $storecfg = cfs_read_file('storage.cfg');
 
     # destroy existing data - keep empty config
-    my $vmcfgfn = config_file($vmid);
+    my $vmcfgfn = PVE::QemuConfig->config_file($vmid);
     destroy_vm($storecfg, $vmid, 1) if -f $vmcfgfn;
 
     my $tocmd = "/usr/lib/qemu-server/qmextract";
@@ -5723,7 +5653,7 @@ sub restore_tar_archive {
     local $ENV{VZDUMP_VMID} = $vmid;
     local $ENV{VZDUMP_USER} = $user;
 
-    my $conffile = config_file($vmid);
+    my $conffile = PVE::QemuConfig->config_file($vmid);
     my $tmpfn = "$conffile.$$.tmp";
 
     # disable interrupts (always do cleanups)
@@ -5799,50 +5729,6 @@ sub restore_tar_archive {
     warn $@ if $@;
 };
 
-
-# Internal snapshots
-
-# NOTE: Snapshot create/delete involves several non-atomic
-# action, and can take a long time.
-# So we try to avoid locking the file and use 'lock' variable
-# inside the config file instead.
-
-my $snapshot_copy_config = sub {
-    my ($source, $dest) = @_;
-
-    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};
-    }
-};
-
-my $snapshot_apply_config = sub {
-    my ($conf, $snap) = @_;
-
-    # copy snapshot list
-    my $newconf = {
-       snapshots => $conf->{snapshots},
-    };
-
-    # keep description and list of unused disks
-    foreach my $k (keys %$conf) {
-       next if !($k =~ m/^unused\d+$/ || $k eq 'description');
-       $newconf->{$k} = $conf->{$k};
-    }
-
-    &$snapshot_copy_config($snap, $newconf);
-
-    return $newconf;
-};
-
 sub foreach_writable_storage {
     my ($conf, $func) = @_;
 
@@ -5866,245 +5752,6 @@ sub foreach_writable_storage {
     }
 }
 
-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;
-};
-
-sub snapshot_save_vmstate {
-    my ($vmid, $conf, $snapname, $storecfg) = @_;
-
-    my $snap = $conf->{snapshots}->{$snapname};
-
-    $snap->{vmstate} = &$alloc_vmstate_volid($storecfg, $vmid, $conf, $snapname);
-    # always overwrite machine if we save vmstate. This makes sure we
-    # can restore it later using correct machine type
-    $snap->{machine} = get_current_qemu_machine($vmid);
-}
-
-sub snapshot_prepare {
-    my ($vmid, $snapname, $save_vmstate, $comment) = @_;
-
-    my $snap;
-
-    my $updatefn =  sub {
-
-       my $conf = load_config($vmid);
-
-       die "you can't take a snapshot if it's a template\n"
-           if is_template($conf);
-
-       check_lock($conf);
-
-       $conf->{lock} = 'snapshot';
-
-       die "snapshot name '$snapname' already used\n"
-           if defined($conf->{snapshots}->{$snapname});
-
-       my $storecfg = PVE::Storage::config();
-       die "snapshot feature is not available\n"
-           if !has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
-
-       $snap = $conf->{snapshots}->{$snapname} = {};
-
-       if ($save_vmstate && check_running($vmid)) {
-           snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
-       }
-
-       &$snapshot_copy_config($conf, $snap);
-
-       $snap->{snapstate} = "prepare";
-       $snap->{snaptime} = time();
-       $snap->{description} = $comment if $comment;
-
-       write_config($vmid, $conf);
-    };
-
-    lock_config($vmid, $updatefn);
-
-    return $snap;
-}
-
-sub snapshot_commit {
-    my ($vmid, $snapname) = @_;
-
-    my $updatefn = sub {
-
-       my $conf = load_config($vmid);
-
-       die "missing snapshot lock\n"
-           if !($conf->{lock} && $conf->{lock} eq 'snapshot');
-
-       my $snap = $conf->{snapshots}->{$snapname};
-
-       die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
-       die "wrong snapshot state\n"
-           if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
-
-       delete $snap->{snapstate};
-       delete $conf->{lock};
-
-       $conf->{parent} = $snapname;
-
-       write_config($vmid, $conf);
-    };
-
-    lock_config($vmid, $updatefn);
-}
-
-sub snapshot_rollback {
-    my ($vmid, $snapname) = @_;
-
-    my $prepare = 1;
-
-    my $storecfg = PVE::Storage::config();
-
-    my $conf = load_config($vmid);
-
-    my $get_snapshot_config = sub {
-
-       die "you can't rollback if vm is a template\n" if is_template($conf);
-
-       my $res = $conf->{snapshots}->{$snapname};
-
-       die "snapshot '$snapname' does not exist\n" if !defined($res);
-
-       return $res;
-    };
-
-    my $snap = &$get_snapshot_config();
-
-    foreach_drive($snap, sub {
-       my ($ds, $drive) = @_;
-
-       return if drive_is_cdrom($drive);
-
-       my $volid = $drive->{file};
-
-       PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
-    });
-
-    my $updatefn = sub {
-
-       $conf = load_config($vmid);
-
-       $snap = &$get_snapshot_config();
-
-       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);
-
-       if ($prepare) {
-           $conf->{lock} = 'rollback';
-       } else {
-           die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
-           delete $conf->{lock};
-       }
-
-       my $forcemachine;
-
-       if (!$prepare) {
-           my $has_machine_config = defined($conf->{machine});
-
-           # copy snapshot config to current config
-           $conf = &$snapshot_apply_config($conf, $snap);
-           $conf->{parent} = $snapname;
-
-           # Note: old code did not store 'machine', so we try to be smart
-           # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
-           $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
-           # we remove the 'machine' configuration if not explicitly specified
-           # in the original config.
-           delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
-       }
-
-       write_config($vmid, $conf);
-
-       if (!$prepare && $snap->{vmstate}) {
-           my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
-           vm_start($storecfg, $vmid, $statefile, undef, undef, undef, $forcemachine);
-       }
-    };
-
-    lock_config($vmid, $updatefn);
-
-    foreach_drive($snap, sub {
-       my ($ds, $drive) = @_;
-
-       return if drive_is_cdrom($drive);
-
-       my $volid = $drive->{file};
-       my $device = "drive-$ds";
-
-       PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
-    });
-
-    $prepare = 0;
-    lock_config($vmid, $updatefn);
-}
-
-my $savevm_wait = sub {
-    my ($vmid) = @_;
-
-    for(;;) {
-       my $stat = 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 do_snapshots_with_qemu {
     my ($storecfg, $volid) = @_;
 
@@ -6133,224 +5780,6 @@ sub qga_check_running {
     return 1;
 }
 
-sub check_freeze_needed {
-    my ($vmid, $config, $save_vmstate) = @_;
-
-    my $running = check_running($vmid);
-    if ($save_vmstate) {
-       return ($running, $running && $config->{agent} && qga_check_running($vmid));
-    } else {
-       return ($running, 0);
-    }
-}
-
-sub snapshot_create {
-    my ($vmid, $snapname, $save_vmstate, $comment) = @_;
-
-    my $snap = snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
-
-    $save_vmstate = 0 if !$snap->{vmstate}; # vm is not running
-
-    my $config = load_config($vmid);
-
-    my ($running, $freezefs) = check_freeze_needed($vmid, $config, $snap->{vmstate});
-
-    my $drivehash = {};
-
-    eval {
-       if ($freezefs) {
-           eval { vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
-           warn "guest-fsfreeze-freeze problems - $@" if $@;
-       }
-
-       # create internal snapshots of all drives
-
-       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");
-           }
-       };
-
-       foreach_drive($snap, sub {
-           my ($ds, $drive) = @_;
-
-           return if drive_is_cdrom($drive);
-
-           my $volid = $drive->{file};
-           my $device = "drive-$ds";
-
-           qemu_volume_snapshot($vmid, $device, $storecfg, $volid, $snapname);
-           $drivehash->{$ds} = 1;
-       });
-    };
-    my $err = $@;
-
-    if ($running) {
-       eval { vm_mon_cmd($vmid, "savevm-end")  };
-       warn $@ if $@;
-
-       if ($freezefs) {
-           eval { vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
-           warn "guest-fsfreeze-thaw problems - $@" if $@;
-       }
-
-       # savevm-end is async, we need to wait
-       for (;;) {
-           my $stat = vm_mon_cmd_nocheck($vmid, "query-savevm");
-           if (!$stat->{bytes}) {
-               last;
-           } else {
-               print "savevm not yet finished\n";
-               sleep(1);
-               next;
-           }
-       }
-    }
-
-    if ($err) {
-       warn "snapshot create failed: starting cleanup\n";
-       eval { snapshot_delete($vmid, $snapname, 1, $drivehash); };
-       warn $@ if $@;
-       die $err;
-    }
-
-    snapshot_commit($vmid, $snapname);
-}
-
-# Note: $drivehash is only set when called from snapshot_create.
-sub snapshot_delete {
-    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);
-
-       if (!$drivehash) {
-           check_lock($conf);
-           die "you can't delete a snapshot if vm is a template\n"
-               if is_template($conf);
-       }
-
-       $snap = $conf->{snapshots}->{$snapname};
-
-       die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
-       # remove parent refs
-       if (!$prepare) {
-           &$unlink_parent($conf, $snap->{parent});
-           foreach my $sn (keys %{$conf->{snapshots}}) {
-               next if $sn eq $snapname;
-               &$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->{snapshots}->{$snapname};
-           delete $conf->{lock} if $drivehash;
-           foreach my $volid (@$unused) {
-               add_unused_volume($conf, $volid);
-           }
-       }
-
-       write_config($vmid, $conf);
-    };
-
-    lock_config($vmid, $updatefn);
-
-    # now remove vmstate file
-
-    my $storecfg = PVE::Storage::config();
-
-    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";
-
-       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;
-    });
-
-    # now cleanup config
-    $prepare = 0;
-    lock_config($vmid, $updatefn);
-}
-
-sub has_feature {
-    my ($feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
-
-    my $err;
-    foreach_drive($conf, sub {
-       my ($ds, $drive) = @_;
-
-       return if drive_is_cdrom($drive);
-       return if $backup_only && !$drive->{backup};
-       my $volid = $drive->{file};
-       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running);
-    });
-
-    return $err ? 0 : 1;
-}
-
 sub template_create {
     my ($vmid, $conf, $disk) = @_;
 
@@ -6368,16 +5797,10 @@ sub template_create {
        my $voliddst = PVE::Storage::vdisk_create_base($storecfg, $volid);
        $drive->{file} = $voliddst;
        $conf->{$ds} = print_drive($vmid, $drive);
-       write_config($vmid, $conf);
+       PVE::QemuConfig->write_config($vmid, $conf);
     });
 }
 
-sub is_template {
-    my ($conf) = @_;
-
-    return 1 if defined $conf->{template} && $conf->{template} == 1;
-}
-
 sub qemu_img_convert {
     my ($src_volid, $dst_volid, $size, $snapname) = @_;