]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
cleanup mp size parameter (add units, same code as QemuServer)
[pve-container.git] / src / PVE / LXC.pm
index 262865156cba385863e8d4ff864150c9e81c83a4..53f77a3a428ce256b21f25ec6b473f4d86a5aa5e 100644 (file)
@@ -12,12 +12,16 @@ use PVE::Storage;
 use PVE::SafeSyslog;
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option);
-use PVE::Tools qw($IPV6RE $IPV4RE);
+use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
 use PVE::Network;
+use PVE::AccessControl;
+use PVE::ProcFSTools;
 
 use Data::Dumper;
 
-cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
+my $nodename = PVE::INotify::nodename();
+
+cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
 
 PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
 sub verify_lxc_network {
@@ -30,410 +34,385 @@ sub verify_lxc_network {
     die "unable to parse network setting\n";
 }
 
-my $nodename = PVE::INotify::nodename();
-
-sub parse_lxc_size {
-    my ($name, $value) = @_;
+PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
+sub verify_ct_mountpoint {
+    my ($value, $noerr) = @_;
 
-    if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
-       my ($res, $unit) = ($1, lc($2 || 'b'));
+    return $value if parse_ct_mountpoint($value);
 
-       return $res if $unit eq 'b';
-       return $res*1024 if $unit eq 'k';
-       return $res*1024*1024 if $unit eq 'm';
-       return $res*1024*1024*1024 if $unit eq 'g';
-    }
+    return undef if $noerr;
 
-    return undef;
+    die "unable to parse CT mountpoint options\n";
 }
 
-my $valid_lxc_keys = {
-    'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
-    'lxc.include' => 1,
-    'lxc.rootfs' => 1,
-    'lxc.mount' => 1,
-    'lxc.utsname' => 1,
-
-    'lxc.id_map' => 1,
+PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
+    type => 'string', format => 'pve-ct-mountpoint',
+    typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
+    description => "Use volume as container root.",
+    optional => 1,
+});
 
-    'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
-    'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
-    'lxc.cgroup.cpu.cfs_period_us' => '\d+',
-    'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
-    'lxc.cgroup.cpu.shares' => '\d+',
+PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
+    description => "The name of the snapshot.",
+    type => 'string', format => 'pve-configid',
+    maxLength => 40,
+});
 
-    # mount related
-    'lxc.mount' => 1,
-    'lxc.mount.entry' => 1,
-    'lxc.mount.auto' => 1,
+my $confdesc = {
+    lock => {
+       optional => 1,
+       type => 'string',
+       description => "Lock/unlock the VM.",
+       enum => [qw(migrate backup snapshot rollback)],
+    },
+    onboot => {
+       optional => 1,
+       type => 'boolean',
+       description => "Specifies whether a VM will be started during system bootup.",
+       default => 0,
+    },
+    startup => get_standard_option('pve-startup-order'),
+    template => {
+       optional => 1,
+       type => 'boolean',
+       description => "Enable/disable Template.",
+       default => 0,
+    },
+    arch => {
+       optional => 1,
+       type => 'string',
+       enum => ['amd64', 'i386'],
+       description => "OS architecture type.",
+       default => 'amd64',
+    },
+    ostype => {
+       optional => 1,
+       type => 'string',
+       enum => ['debian', 'ubuntu', 'centos', 'archlinux'],
+       description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
+    },
+    console => {
+       optional => 1,
+       type => 'boolean',
+       description => "Attach a console device (/dev/console) to the container.",
+       default => 1,
+    },
+    tty => {
+       optional => 1,
+       type => 'integer',
+       description => "Specify the number of tty available to the container",
+       minimum => 0,
+       maximum => 6,
+       default => 2,
+    },
+    cpulimit => {
+       optional => 1,
+       type => 'number',
+       description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
+       minimum => 0,
+       maximum => 128,
+       default => 0,
+    },
+    cpuunits => {
+       optional => 1,
+       type => 'integer',
+       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+       minimum => 0,
+       maximum => 500000,
+       default => 1024,
+    },
+    memory => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of RAM for the VM in MB.",
+       minimum => 16,
+       default => 512,
+    },
+    swap => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of SWAP for the VM in MB.",
+       minimum => 0,
+       default => 512,
+    },
+    hostname => {
+       optional => 1,
+       description => "Set a host name for the container.",
+       type => 'string',
+       maxLength => 255,
+    },
+    description => {
+       optional => 1,
+       type => 'string',
+       description => "Container description. Only used on the configuration web interface.",
+    },
+    searchdomain => {
+       optional => 1,
+       type => 'string',
+       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
+    },
+    nameserver => {
+       optional => 1,
+       type => 'string',
+       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
+    },
+    rootfs => get_standard_option('pve-ct-rootfs'),
+    parent => {
+       optional => 1,
+       type => 'string', format => 'pve-configid',
+       maxLength => 40,
+       description => "Parent snapshot name. This is used internally, and should not be modified.",
+    },
+    snaptime => {
+       optional => 1,
+       description => "Timestamp for snapshots.",
+       type => 'integer',
+       minimum => 0,
+    },
+    cmode => {
+       optional => 1,
+       description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
+       type => 'string',
+       enum => ['shell', 'console', 'tty'],
+       default => 'tty',
+    },
+    protection => {
+       optional => 1,
+       type => 'boolean',
+       description => "Sets the protection flag of the container. This will prevent the remove operation.",
+       default => 0,
+    },
+};
 
-    # not used by pve
-    'lxc.tty' => '\d+',
-    'lxc.pts' => 1,
+my $valid_lxc_conf_keys = {
+    'lxc.include' => 1,
+    'lxc.arch' => 1,
+    'lxc.utsname' => 1,
     'lxc.haltsignal' => 1,
     'lxc.rebootsignal' => 1,
     'lxc.stopsignal' => 1,
     'lxc.init_cmd' => 1,
-    'lxc.console' => 1,
+    'lxc.network.type' => 1,
+    'lxc.network.flags' => 1,
+    'lxc.network.link' => 1,
+    'lxc.network.mtu' => 1,
+    'lxc.network.name' => 1,
+    'lxc.network.hwaddr' => 1,
+    'lxc.network.ipv4' => 1,
+    'lxc.network.ipv4.gateway' => 1,
+    'lxc.network.ipv6' => 1,
+    'lxc.network.ipv6.gateway' => 1,
+    'lxc.network.script.up' => 1,
+    'lxc.network.script.down' => 1,
+    'lxc.pts' => 1,
     'lxc.console.logfile' => 1,
+    'lxc.console' => 1,
+    'lxc.tty' => 1,
     'lxc.devttydir' => 1,
+    'lxc.hook.autodev' => 1,
     'lxc.autodev' => 1,
     'lxc.kmsg' => 1,
+    'lxc.mount' => 1,
+    'lxc.mount.entry' => 1,
+    'lxc.mount.auto' => 1,
+    'lxc.rootfs' => 1,
+    'lxc.rootfs.mount' => 1,
+    'lxc.rootfs.options' => 1,
+    # lxc.cgroup.*
     'lxc.cap.drop' => 1,
     'lxc.cap.keep' => 1,
     'lxc.aa_profile' => 1,
     'lxc.aa_allow_incomplete' => 1,
     'lxc.se_context' => 1,
-    'lxc.loglevel' => 1,
-    'lxc.logfile' => 1,
-    'lxc.environment' => 1,
-    'lxc.cgroup.devices.deny' => 1,
-
-    # autostart
-    'lxc.start.auto' => 1,
-    'lxc.start.delay' => 1,
-    'lxc.start.order' => 1,
-    'lxc.group' => 1,
-
-    # hooks
+    'lxc.seccomp' => 1,
+    'lxc.id_map' => 1,
     'lxc.hook.pre-start' => 1,
     'lxc.hook.pre-mount' => 1,
     'lxc.hook.mount' => 1,
-    'lxc.hook.autodev' => 1,
     'lxc.hook.start' => 1,
     'lxc.hook.post-stop' => 1,
     'lxc.hook.clone' => 1,
-
-    # pve related keys
-    'pve.nameserver' => sub {
-       my ($name, $value) = @_;
-       return verify_nameserver_list($value);
-    },
-    'pve.searchdomain' => sub {
-       my ($name, $value) = @_;
-       return verify_searchdomain_list($value);
-    },
-    'pve.onboot' => '(0|1)',
-    'pve.startup' => sub {
-       my ($name, $value) = @_;
-       return PVE::JSONSchema::pve_verify_startup_order($value);
-    },
-    'pve.comment' => 1,
-    'pve.disksize' => '\d+(\.\d+)?',
-    'pve.volid' => sub {
-       my ($name, $value) = @_;
-       PVE::Storage::parse_volume_id($value);
-       return $value;
-    },
-
-     #pve snapshot
-    'pve.lock' => 1,
-    'pve.snaptime' => 1,
-    'pve.snapcomment' => 1,
-    'pve.parent' => 1,
-    'pve.snapstate' => 1,
-    'pve.snapname' => 1,
+    'lxc.hook.destroy' => 1,
+    'lxc.loglevel' => 1,
+    'lxc.logfile' => 1,
+    'lxc.start.auto' => 1,
+    'lxc.start.delay' => 1,
+    'lxc.start.order' => 1,
+    'lxc.group' => 1,
+    'lxc.environment' => 1,
+    'lxc.' => 1,
+    'lxc.' => 1,
+    'lxc.' => 1,
+    'lxc.' => 1,
 };
 
-my $valid_lxc_network_keys = {
-    type => 1,
-    mtu => 1,
-    name => 1, # ifname inside container
-    'veth.pair' => 1, # ifname at host (eth${vmid}.X)
-    hwaddr => 1,
-};
+my $MAX_LXC_NETWORKS = 10;
+for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
+    $confdesc->{"net$i"} = {
+       optional => 1,
+       type => 'string', format => 'pve-lxc-network',
+       description => "Specifies network interfaces for the container.\n\n".
+           "The string should have the follow format:\n\n".
+           "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
+           "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
+           ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
+           ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
+    };
+}
 
-my $valid_pve_network_keys = {
-    bridge => 1,
-    tag => 1,
-    firewall => 1,
-    ip => 1,
-    gw => 1,
-    ip6 => 1,
-    gw6 => 1,
-};
+my $MAX_MOUNT_POINTS = 10;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"mp$i"} = {
+       optional => 1,
+       type => 'string', format => 'pve-ct-mountpoint',
+       typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
+       description => "Use volume as container mount point (experimental feature).",
+       optional => 1,
+    };
+}
 
-my $lxc_array_configs = {
-    'lxc.network' => 1,
-    'lxc.mount' => 1,
-    'lxc.include' => 1,
-    'lxc.id_map' => 1,
-    'lxc.cgroup.devices.deny' => 1,
-};
+sub write_pct_config {
+    my ($filename, $conf) = @_;
 
-sub write_lxc_config {
-    my ($filename, $data) = @_;
+    delete $conf->{snapstate}; # just to be sure
 
-    my $raw = "";
+    my $generate_raw_config = sub {
+       my ($conf) = @_;
 
-    return $raw if !$data;
+       my $raw = '';
 
-    my $dump_entry = sub {
-       my ($k, $value, $done_hash, $snapshot) = @_;
-       return if !defined($value);
-       return if $done_hash->{$k};
-       $done_hash->{$k} = 1;
-       if (ref($value)) {
-           die "got unexpected reference for '$k'"
-               if !$lxc_array_configs->{$k};
-           foreach my $v (@$value) {
-               $raw .= 'snap.' if $snapshot;
-               $raw .= "$k = $v\n";
-           }
-       } else {
-           $raw .= 'snap.' if $snapshot;
-           $raw .= "$k = $value\n";
+       # add description as comment to top of file
+       my $descr = $conf->{description} || '';
+       foreach my $cl (split(/\n/, $descr)) {
+           $raw .= '#' .  PVE::Tools::encode_text($cl) . "\n";
        }
-    };
-
-    my $config_writer = sub {
-       my ($elem, $snapshot) = @_;
 
-       my $done_hash = { digest => 1};
-
-       if (defined(my $value = $elem->{'pve.snapname'})) {
-            &$dump_entry('pve.snapname', $value, $done_hash, $snapshot);
+       foreach my $key (sort keys %$conf) {
+           next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || 
+               $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
+           $raw .= "$key: $conf->{$key}\n";
        }
 
-       # Note: Order is important! Include defaults first, so that we
-       # can overwrite them later.
-       &$dump_entry('lxc.include', $elem->{'lxc.include'}, $done_hash, $snapshot);
-
-       foreach my $k (sort keys %$elem) {
-           next if $k !~ m/^lxc\./;
-           &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
-       }
-       foreach my $k (sort keys %$elem) {
-           next if $k !~ m/^pve\./;
-           &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
-       }
-       my $network_count = 0;
-
-       foreach my $k (sort keys %$elem) {
-           next if $k !~ m/^net\d+$/;
-           $done_hash->{$k} = 1;
-
-           my $net = $elem->{$k};
-           $network_count++;
-           $raw .= 'snap.' if $snapshot;
-           $raw .= "lxc.network.type = $net->{type}\n";
-           foreach my $subkey (sort keys %$net) {
-               next if $subkey eq 'type';
-               if ($valid_lxc_network_keys->{$subkey}) {
-                   $raw .= 'snap.' if $snapshot;
-                   $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
-               } elsif ($valid_pve_network_keys->{$subkey}) {
-                   $raw .= 'snap.' if $snapshot;
-                   $raw .= "pve.network.$subkey = $net->{$subkey}\n";
-               } else {
-                   die "found invalid network key '$subkey'";
-               }
+       if (my $lxcconf = $conf->{lxc}) {
+           foreach my $entry (@$lxcconf) {
+               my ($k, $v) = @$entry;
+               $raw .= "$k: $v\n";
            }
        }
-       if (!$network_count) {
-           $raw .= 'snap.' if $snapshot;
-           $raw .= "lxc.network.type = empty\n";
-       }
-       foreach my $k (sort keys %$elem) {
-           next if $k eq 'snapshots';
-           next if $done_hash->{$k};
-           die "found un-written value \"$k\" in config - implement this!";
-       }
-
+       
+       return $raw;
     };
 
-    &$config_writer($data);
+    my $raw = &$generate_raw_config($conf);
 
-    if ($data->{snapshots}) {
-       my @tmp = sort { $data->{snapshots}->{$b}{'pve.snaptime'} <=>
-                             $data->{snapshots}->{$a}{'pve.snaptime'} }
-                       keys %{$data->{snapshots}};
-       foreach my $snapname (@tmp) {
-           $raw .= "\n";
-           &$config_writer($data->{snapshots}->{$snapname}, 1);
-       }
+    foreach my $snapname (sort keys %{$conf->{snapshots}}) {
+       $raw .= "\n[$snapname]\n";
+       $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
     }
 
     return $raw;
 }
 
-sub parse_lxc_option {
-    my ($name, $value) = @_;
+sub check_type {
+    my ($key, $value) = @_;
 
-    my $parser = $valid_lxc_keys->{$name};
+    die "unknown setting '$key'\n" if !$confdesc->{$key};
 
-    die "invalid key '$name'\n" if !defined($parser);
+    my $type = $confdesc->{$key}->{type};
 
-    if ($parser eq '1') {
+    if (!defined($value)) {
+       die "got undefined value\n";
+    }
+
+    if ($value =~ m/[\n\r]/) {
+       die "property contains a line feed\n";
+    }
+
+    if ($type eq 'boolean') {
+       return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
+       return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
+       die "type check ('boolean') failed - got '$value'\n";
+    } elsif ($type eq 'integer') {
+       return int($1) if $value =~ m/^(\d+)$/;
+       die "type check ('integer') failed - got '$value'\n";
+    } elsif ($type eq 'number') {
+        return $value if $value =~ m/^(\d+)(\.\d+)?$/;
+        die "type check ('number') failed - got '$value'\n";
+    } elsif ($type eq 'string') {
+       if (my $fmt = $confdesc->{$key}->{format}) {
+           PVE::JSONSchema::check_format($fmt, $value);
+           return $value;
+       }
        return $value;
-    } elsif (ref($parser)) {
-       my $res = &$parser($name, $value);
-       return $res if defined($res);
     } else {
-       # assume regex
-       return $value if $value =~ m/^$parser$/;
+       die "internal error"
     }
-
-    die "unable to parse value '$value' for option '$name'\n";
 }
 
-sub parse_lxc_config {
+sub parse_pct_config {
     my ($filename, $raw) = @_;
 
     return undef if !defined($raw);
 
-    my $data = {
+    my $res = {
        digest => Digest::SHA::sha1_hex($raw),
+       snapshots => {},
     };
 
-    $filename =~ m|/lxc/(\d+)/config$|
+    $filename =~ m|/lxc/(\d+).conf$|
        || die "got strange filename '$filename'";
 
     my $vmid = $1;
 
-    my $split_config = sub {
-       my ($raw) = @_;
-       my $sections = [];
-       my $tmp = '';
-       while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
-           my $line = $1;
-           if(!$line) {
-               push(@{$sections},$tmp);
-               $tmp = '';
-           } else {
-               $tmp .= "$line\n";
-           }
-       }
-       push(@{$sections},$tmp);
-
-       return $sections;
-    };
-
-    my $sec = &$split_config($raw);
-
-    foreach my  $sec_raw (@{$sec}){
-       next if $sec_raw eq '';
-       my $snapname = undef;
+    my $conf = $res;
+    my $descr = '';
+    my $section = '';
 
-       my $network_counter = 0;
-       my $network_list = [];
-       my $host_ifnames = {};
+    my @lines = split(/\n/, $raw);
+    foreach my $line (@lines) {
+       next if $line =~ m/^\s*$/;
 
-       my $find_next_hostif_name = sub {
-           for (my $i = 0; $i < 10; $i++) {
-               my $name = "veth${vmid}.$i";
-               if (!$host_ifnames->{$name}) {
-                   $host_ifnames->{$name} = 1;
-                   return $name;
-               }
-           }
-
-           die "unable to find free host_ifname"; # should not happen
-       };
-
-       my $push_network = sub {
-           my ($netconf) = @_;
-           return if !$netconf;
-           push @{$network_list}, $netconf;
-           $network_counter++;
-           if (my $netname = $netconf->{'veth.pair'}) {
-               if ($netname =~ m/^veth(\d+).(\d)$/) {
-                   die "wrong vmid for network interface pair\n" if $1 != $vmid;
-                   my $host_ifnames->{$netname} = 1;
-               } else {
-                   die "wrong network interface pair\n";
-               }
-           }
-       };
-
-       my $network;
-
-       while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
-           my $line = $1;
-
-           next if $line =~ m/^\#/;
-           next if $line =~ m/^\s*$/;
-
-           if ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-               my ($subkey, $value) = ($2, $3);
-               if ($subkey eq 'type') {
-                   &$push_network($network);
-                   $network = { type => $value };
-               } elsif ($valid_lxc_network_keys->{$subkey}) {
-                   $network->{$subkey} = $value;
-               } else {
-                   die "unable to parse config line: $line\n";
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-               my ($subkey, $value) = ($2, $3);
-               if ($valid_pve_network_keys->{$subkey}) {
-                   $network->{$subkey} = $value;
-               } else {
-                   die "unable to parse config line: $line\n";
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) {
-               my ($name, $value) = ($2, $3);
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{$name} = $value;
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) {
-               if (!$snapname) {
-                   $snapname = $2;
-                   $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
-               } else {
-                   die "Configuarion broken\n";
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
-               my ($name, $value) = ($2, $3);
-
-               if ($lxc_array_configs->{$name}) {
-                   $data->{$name} = [] if !defined($data->{$name});
-                   if ($snapname) {
-                       push @{$data->{snapshots}->{$snapname}->{$name}},  parse_lxc_option($name, $value);
-                   } else {
-                       push @{$data->{$name}},  parse_lxc_option($name, $value);
-                   }
-               } else {
-                   if ($snapname) {
-                       die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
-                       $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
-                   } else {
-                       die "multiple definitions for $name\n" if defined($data->{$name});
-                       $data->{$name} = parse_lxc_option($name, $value);
-                   }
-               }
-
-               next;
-           }
-           die "unable to parse config line: $line\n";
+       if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
+           $section = $1;
+           $conf->{description} = $descr if $descr;
+           $descr = '';
+           $conf = $res->{snapshots}->{$section} = {};
+           next;
        }
-       &$push_network($network);
 
-       foreach my $net (@{$network_list}) {
-           next if $net->{type} eq 'empty'; # skip
-           $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
-           $net->{hwaddr} =  PVE::Tools::random_ether_addr() if !$net->{hwaddr};
-           die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
+       if ($line =~ m/^\#(.*)\s*$/) {
+           $descr .= PVE::Tools::decode_text($1) . "\n";
+           next;
+       }
 
-           if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{"net$1"} = $net;
-               } else {
-                   $data->{"net$1"} = $net;
-               }
+       if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
+           my $key = $1;
+           my $value = $3;
+           if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
+               push @{$conf->{lxc}}, [$key, $value];
+           } else {
+               warn "vm $vmid - unable to parse config: $line\n";
            }
+       } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
+           $descr .= PVE::Tools::decode_text($2);
+       } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
+           $conf->{snapstate} = $1;
+       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
+           my $key = $1;
+           my $value = $2;
+           eval { $value = check_type($key, $value); };
+           warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
+           $conf->{$key} = $value;
+       } else {
+           warn "vm $vmid - unable to parse config: $line\n";
        }
     }
 
-    return $data;
+    $conf->{description} = $descr if $descr;
+
+    delete $res->{snapstate}; # just to be sure
+
+    return $res;
 }
 
 sub config_list {
@@ -456,7 +435,7 @@ sub cfs_config_path {
     my ($vmid, $node) = @_;
 
     $node = $nodename if !$node;
-    return "nodes/$node/lxc/$vmid/config";
+    return "nodes/$node/lxc/$vmid.conf";
 }
 
 sub config_file {
@@ -467,9 +446,10 @@ sub config_file {
 }
 
 sub load_config {
-    my ($vmid) = @_;
+    my ($vmid, $node) = @_;
 
-    my $cfspath = cfs_config_path($vmid);
+    $node = $nodename if !$node;
+    my $cfspath = cfs_config_path($vmid, $node);
 
     my $conf = PVE::Cluster::cfs_read_file($cfspath);
     die "container $vmid does not exists\n" if !defined($conf);
@@ -483,17 +463,13 @@ sub create_config {
     my $dir = "/etc/pve/nodes/$nodename/lxc";
     mkdir $dir;
 
-    $dir .= "/$vmid";
-    mkdir($dir) || die "unable to create container configuration directory - $!\n";
-
     write_config($vmid, $conf);
 }
 
 sub destroy_config {
     my ($vmid) = @_;
 
-    my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
-    File::Path::rmtree($dir);
+    unlink config_file($vmid, $nodename);
 }
 
 sub write_config {
@@ -504,20 +480,6 @@ sub write_config {
     PVE::Cluster::cfs_write_file($cfspath, $conf);
 }
 
-my $tempcounter = 0;
-sub write_temp_config {
-    my ($vmid, $conf) = @_;
-
-    $tempcounter++;
-    my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
-
-    my $raw =  write_lxc_config($filename, $conf);
-
-    PVE::Tools::file_set_contents($filename, $raw);
-
-    return $filename;
-}
-
 # flock: we use one file handle per process, so lock file
 # can be called multiple times and succeeds for the same process.
 
@@ -527,7 +489,7 @@ my $lockdir = "/run/lock/lxc";
 sub lock_filename {
     my ($vmid) = @_;
 
-    return "$lockdir/pve-config-{$vmid}.lock";
+    return "$lockdir/pve-config-${vmid}.lock";
 }
 
 sub lock_aquire {
@@ -562,10 +524,10 @@ sub lock_aquire {
                die "can't aquire lock - $!\n";
            }
 
-           $lock_handles->{$$}->{$filename}->{refcount}++;
-
            print STDERR " OK\n";
        }
+       
+       $lock_handles->{$$}->{$filename}->{refcount}++;
     };
 
     eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
@@ -604,88 +566,6 @@ sub lock_container {
     return $res;
 }
 
-my $confdesc = {
-    onboot => {
-       optional => 1,
-       type => 'boolean',
-       description => "Specifies whether a VM will be started during system bootup.",
-       default => 0,
-    },
-    startup => get_standard_option('pve-startup-order'),
-    cpulimit => {
-       optional => 1,
-       type => 'number',
-       description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
-       minimum => 0,
-       maximum => 128,
-       default => 0,
-    },
-    cpuunits => {
-       optional => 1,
-       type => 'integer',
-       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
-       minimum => 0,
-       maximum => 500000,
-       default => 1000,
-    },
-    memory => {
-       optional => 1,
-       type => 'integer',
-       description => "Amount of RAM for the VM in MB.",
-       minimum => 16,
-       default => 512,
-    },
-    swap => {
-       optional => 1,
-       type => 'integer',
-       description => "Amount of SWAP for the VM in MB.",
-       minimum => 0,
-       default => 512,
-    },
-    disk => {
-       optional => 1,
-       type => 'number',
-       description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
-       minimum => 0,
-       default => 4,
-    },
-    hostname => {
-       optional => 1,
-       description => "Set a host name for the container.",
-       type => 'string',
-       maxLength => 255,
-    },
-    description => {
-       optional => 1,
-       type => 'string',
-       description => "Container description. Only used on the configuration web interface.",
-    },
-    searchdomain => {
-       optional => 1,
-       type => 'string',
-       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
-    },
-    nameserver => {
-       optional => 1,
-       type => 'string',
-       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
-    },
-};
-
-my $MAX_LXC_NETWORKS = 10;
-for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
-    $confdesc->{"net$i"} = {
-       optional => 1,
-       type => 'string', format => 'pve-lxc-network',
-       description => "Specifies network interfaces for the container.\n\n".
-           "The string should have the follow format:\n\n".
-           "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
-           "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
-           ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
-           ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
-    };
-}
-
 sub option_exists {
     my ($name) = @_;
 
@@ -697,6 +577,20 @@ sub json_config_properties {
     my $prop = shift;
 
     foreach my $opt (keys %$confdesc) {
+       next if $opt eq 'parent' || $opt eq 'snaptime';
+       next if $prop->{$opt};
+       $prop->{$opt} = $confdesc->{$opt};
+    }
+
+    return $prop;
+}
+
+sub json_config_properties_no_rootfs {
+    my $prop = shift;
+
+    foreach my $opt (keys %$confdesc) {
+       next if $prop->{$opt};
+       next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
        $prop->{$opt} = $confdesc->{$opt};
     }
 
@@ -718,7 +612,7 @@ sub list_active_containers {
     while (defined(my $line = <$fh>)) {
        if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
            my $path = $1;
-           if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
+           if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
                $res->{$1} = 1;
            }
        }
@@ -785,40 +679,30 @@ sub vmstatus {
        my $cfspath = cfs_config_path($vmid);
        my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
 
-       $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
+       $d->{name} = $conf->{'hostname'} || "CT$vmid";
        $d->{name} =~ s/[\s]//g;
 
-       $d->{cpus} = 0;
-
-       my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
-       my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
-
-       if ($cfs_period_us && $cfs_quota_us) {
-           $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
-       }
+       $d->{cpus} = $conf->{cpulimit} // 0;
 
-       $d->{disk} = 0;
-       $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
-           int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
-
-       if (my $private = $conf->{'lxc.rootfs'}) {
-           if ($private =~ m!^/!) {
-               my $res = PVE::Tools::df($private, 2);
-               $d->{disk} = $res->{used};
-               $d->{maxdisk} = $res->{total};
-           } elsif ($running) {
-               if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
-                   my $res = get_container_disk_usage($vmid);
-                   $d->{disk} = $res->{used};
-                   $d->{maxdisk} = $res->{total};
-               }
+       if ($running) {
+           my $res = get_container_disk_usage($vmid);
+           $d->{disk} = $res->{used};
+           $d->{maxdisk} = $res->{total};
+       } else {
+           $d->{disk} = 0;
+           # use 4GB by default ??
+           if (my $rootfs = $conf->{rootfs}) {
+               my $rootinfo = parse_ct_mountpoint($rootfs);
+               $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
+           } else {
+               $d->{maxdisk} = 4*1024*1024*1024;
            }
        }
 
        $d->{mem} = 0;
        $d->{swap} = 0;
-       $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
-           ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
+       $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
+       $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
 
        $d->{uptime} = 0;
        $d->{cpu} = 0;
@@ -828,6 +712,8 @@ sub vmstatus {
 
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
+
+       $d->{template} = is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
@@ -852,6 +738,93 @@ sub vmstatus {
     return $list;
 }
 
+my $parse_size = sub {
+    my ($value) = @_;
+
+    return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
+    my ($size, $unit) = ($1, $3);
+    if ($unit) {
+       if ($unit eq 'K') {
+           $size = $size * 1024;
+       } elsif ($unit eq 'M') {
+           $size = $size * 1024 * 1024;
+       } elsif ($unit eq 'G') {
+           $size = $size * 1024 * 1024 * 1024;
+       }
+    }
+    return int($size);
+};
+
+my $format_size = sub {
+    my ($size) = @_;
+
+    $size = int($size);
+
+    my $kb = int($size/1024);
+    return $size if $kb*1024 != $size;
+
+    my $mb = int($kb/1024);
+    return "${kb}K" if $mb*1024 != $kb;
+
+    my $gb = int($mb/1024);
+    return "${mb}M" if $gb*1024 != $mb;
+
+    return "${gb}G";
+};
+
+sub parse_ct_mountpoint {
+    my ($data) = @_;
+
+    $data //= '';
+
+    my $res = {};
+
+    foreach my $p (split (/,/, $data)) {
+       next if $p =~ m/^\s*$/;
+
+       if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
+           my ($k, $v) = ($1, $2);
+           return undef if defined($res->{$k});
+           $res->{$k} = $v;
+       } else {
+           if (!$res->{volume} && $p !~ m/=/) {
+               $res->{volume} = $p;
+           } else {
+               return undef;
+           }
+       }
+    }
+
+    return undef if !defined($res->{volume});
+
+    return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
+
+    if ($res->{size}) {
+       return undef if !defined($res->{size} = &$parse_size($res->{size}));
+    }
+
+    return $res;
+}
+
+sub print_ct_mountpoint {
+    my ($info, $nomp) = @_;
+
+    my $opts = '';
+
+    die "missing volume\n" if !$info->{volume};
+
+    foreach my $o (qw(backup)) {
+       $opts .= ",$o=$info->{$o}" if defined($info->{$o});
+    }
+
+    if ($info->{size}) {
+       $opts .= ",size=" . &$format_size($info->{size});
+    }
+
+    $opts .= ",mp=$info->{mp}" if !$nomp;
+
+    return "$info->{volume}$opts";
+}
 
 sub print_lxc_network {
     my $net = shift;
@@ -1001,75 +974,102 @@ sub parse_ipv4_cidr {
 sub check_lock {
     my ($conf) = @_;
 
-    die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
+    die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
 }
 
-sub lxc_conf_to_pve {
-    my ($vmid, $lxc_conf) = @_;
+sub update_lxc_config {
+    my ($storage_cfg, $vmid, $conf) = @_;
 
-    my $properties = json_config_properties();
+    my $dir = "/var/lib/lxc/$vmid";
 
-    my $conf = { digest => $lxc_conf->{digest} };
+    if ($conf->{template}) {
 
-    foreach my $k (keys %$properties) {
+       unlink "$dir/config";
 
-       if ($k eq 'description') {
-           if (my $raw = $lxc_conf->{'pve.comment'}) {
-               $conf->{$k} = PVE::Tools::decode_text($raw);
-           }
-       } elsif ($k eq 'onboot') {
-           $conf->{$k} = $lxc_conf->{'pve.onboot'} if  $lxc_conf->{'pve.onboot'};
-       } elsif ($k eq 'startup') {
-           $conf->{$k} = $lxc_conf->{'pve.startup'} if  $lxc_conf->{'pve.startup'};
-       } elsif ($k eq 'hostname') {
-           $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
-       } elsif ($k eq 'nameserver') {
-           $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
-       } elsif ($k eq 'searchdomain') {
-           $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
-       } elsif ($k eq 'memory') {
-           if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
-               $conf->{$k} = int($value / (1024*1024));
-           }
-       } elsif ($k eq 'swap') {
-           if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
-               my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
-               $conf->{$k} = int(($value -$mem) / (1024*1024));
-           }
-       } elsif ($k eq 'cpulimit') {
-           my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
-           my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
+       return;
+    }
 
-           if ($cfs_period_us && $cfs_quota_us) {
-               $conf->{$k} = $cfs_quota_us/$cfs_period_us;
-           } else {
-               $conf->{$k} = 0;
-           }
-       } elsif ($k eq 'cpuunits') {
-           $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
-       } elsif ($k eq 'disk') {
-           $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
-               $lxc_conf->{'pve.disksize'} : 0;
-       } elsif ($k =~ m/^net\d$/) {
-           my $net = $lxc_conf->{$k};
-           next if !$net;
-           $conf->{$k} = print_lxc_network($net);
-       }
+    my $raw = '';
+
+    die "missing 'arch' - internal error" if !$conf->{arch};
+    $raw .= "lxc.arch = $conf->{arch}\n";
+
+    my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
+    if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
+       $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
+    } else {
+       die "implement me";
+    }
+
+    if (!has_dev_console($conf)) {
+       $raw .= "lxc.console = none\n";
+       $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
+    }
+
+    my $ttycount = get_tty_count($conf);
+    $raw .= "lxc.tty = $ttycount\n";
+
+    my $utsname = $conf->{hostname} || "CT$vmid";
+    $raw .= "lxc.utsname = $utsname\n";
+
+    my $memory = $conf->{memory} || 512;
+    my $swap = $conf->{swap} // 0;
+
+    my $lxcmem = int($memory*1024*1024);
+    $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
+
+    my $lxcswap = int(($memory + $swap)*1024*1024);
+    $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
+
+    if (my $cpulimit = $conf->{cpulimit}) {
+       $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
+       my $value = int(100000*$cpulimit);
+       $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
     }
 
-    if (my $parent = $lxc_conf->{'pve.parent'}) {
-           $conf->{parent} = $lxc_conf->{'pve.parent'};
+    my $shares = $conf->{cpuunits} || 1024;
+    $raw .= "lxc.cgroup.cpu.shares = $shares\n";
+
+    my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
+    $mountpoint->{mp} = '/';
+    my $volid = $mountpoint->{volume};
+    my $path = mountpoint_mount_path($mountpoint, $storage_cfg);
+    
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+    if ($storage) {
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
+       $path = "loop:$path" if $scfg->{path};
     }
 
-    if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
-       $conf->{description} = $lxc_conf->{'pve.snapcomment'};
+    $raw .= "lxc.rootfs = $path\n";
+
+    my $netcount = 0;
+    foreach my $k (keys %$conf) {
+       next if $k !~ m/^net(\d+)$/;
+       my $ind = $1;
+       my $d = parse_lxc_network($conf->{$k});
+       $netcount++;
+       $raw .= "lxc.network.type = veth\n";
+       $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
+       $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
+       $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
+       $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
     }
 
-    if (my $parent = $lxc_conf->{'pve.snaptime'}) {
-       $conf->{snaptime} = $lxc_conf->{'pve.snaptime'};
+    if (my $lxcconf = $conf->{lxc}) {
+       foreach my $entry (@$lxcconf) {
+           my ($k, $v) = @$entry;
+           $netcount++ if $k eq 'lxc.network.type';
+           $raw .= "$k = $v\n";
+       }
     }
 
-    return $conf;
+    $raw .= "lxc.network.type = empty\n" if !$netcount;
+    
+    File::Path::mkpath("$dir/rootfs");
+
+    PVE::Tools::file_set_contents("$dir/config", $raw);
 }
 
 # verify and cleanup nameserver list (replace \0 with ' ')
@@ -1097,11 +1097,13 @@ sub verify_searchdomain_list {
     return join(' ', @list);
 }
 
-sub update_lxc_config {
+sub update_pct_config {
     my ($vmid, $conf, $running, $param, $delete) = @_;
 
     my @nohotplug;
 
+    my $new_disks = [];
+
     my $rootdir;
     if ($running) {
        my $pid = find_lxc_pid($vmid);
@@ -1110,101 +1112,155 @@ sub update_lxc_config {
 
     if (defined($delete)) {
        foreach my $opt (@$delete) {
-           if ($opt eq 'hostname' || $opt eq 'memory') {
+           if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
                die "unable to delete required option '$opt'\n";
            } elsif ($opt eq 'swap') {
-               delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
+               delete $conf->{$opt};
                write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
-           } elsif ($opt eq 'description') {
-               delete $conf->{'pve.comment'};
-           } elsif ($opt eq 'onboot') {
-               delete $conf->{'pve.onboot'};
-           } elsif ($opt eq 'startup') {
-               delete $conf->{'pve.startup'};
-           } elsif ($opt eq 'nameserver') {
-               delete $conf->{'pve.nameserver'};
-               push @nohotplug, $opt;
-               next if $running;
-           } elsif ($opt eq 'searchdomain') {
-               delete $conf->{'pve.searchdomain'};
+           } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
+               delete $conf->{$opt};
+           } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
+                    $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+               delete $conf->{$opt};
                push @nohotplug, $opt;
                next if $running;
            } elsif ($opt =~ m/^net(\d)$/) {
                delete $conf->{$opt};
                next if !$running;
                my $netid = $1;
-               PVE::Network::veth_delete("veth${vmid}.$netid");
+               PVE::Network::veth_delete("veth${vmid}i$netid");
+           } elsif ($opt eq 'protection') {
+               delete $conf->{$opt};
+           } elsif ($opt =~ m/^mp(\d+)$/) {
+               delete $conf->{$opt};
+               push @nohotplug, $opt;
+               next if $running;
+           } elsif ($opt eq 'rootfs') {
+               die "implement me"
            } else {
                die "implement me"
            }
-           PVE::LXC::write_config($vmid, $conf) if $running;
+           write_config($vmid, $conf) if $running;
+       }
+    }
+
+    # There's no separate swap size to configure, there's memory and "total"
+    # memory (iow. memory+swap). This means we have to change them together.
+    my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
+    my $wanted_swap =  PVE::Tools::extract_param($param, 'swap');
+    if (defined($wanted_memory) || defined($wanted_swap)) {
+
+       $wanted_memory //= ($conf->{memory} || 512);
+       $wanted_swap //=  ($conf->{swap} || 0);
+
+        my $total = $wanted_memory + $wanted_swap;
+       if ($running) {
+           write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
+           write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
        }
+       $conf->{memory} = $wanted_memory;
+       $conf->{swap} = $wanted_swap;
+
+       write_config($vmid, $conf) if $running;
     }
 
     foreach my $opt (keys %$param) {
        my $value = $param->{$opt};
        if ($opt eq 'hostname') {
-           $conf->{'lxc.utsname'} = $value;
+           $conf->{$opt} = $value;
        } elsif ($opt eq 'onboot') {
-           $conf->{'pve.onboot'} = $value ? 1 : 0;
+           $conf->{$opt} = $value ? 1 : 0;
        } elsif ($opt eq 'startup') {
-           $conf->{'pve.startup'} = $value;
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+           $conf->{$opt} = $value;
+           push @nohotplug, $opt;
+           next if $running;
        } elsif ($opt eq 'nameserver') {
            my $list = verify_nameserver_list($value);
-           $conf->{'pve.nameserver'} = $list;
+           $conf->{$opt} = $list;
            push @nohotplug, $opt;
            next if $running;
        } elsif ($opt eq 'searchdomain') {
            my $list = verify_searchdomain_list($value);
-           $conf->{'pve.searchdomain'} = $list;
+           $conf->{$opt} = $list;
            push @nohotplug, $opt;
            next if $running;
-       } elsif ($opt eq 'memory') {
-           $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
-           write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
-       } elsif ($opt eq 'swap') {
-           my $mem =  $conf->{'lxc.cgroup.memory.limit_in_bytes'};
-           $mem = $param->{memory}*1024*1024 if $param->{memory};
-           $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
-           write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
-
        } elsif ($opt eq 'cpulimit') {
-           if ($value > 0) {
-               my $cfs_period_us = 100000;
-               $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
-               $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
-               write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
-           } else {
-               delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
-               delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
-               write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
-           }
+           $conf->{$opt} = $value;
+           push @nohotplug, $opt; # fixme: hotplug
+           next;
        } elsif ($opt eq 'cpuunits') {
-           $conf->{'lxc.cgroup.cpu.shares'} = $value;
+           $conf->{$opt} = $value;
            write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
        } elsif ($opt eq 'description') {
-           $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
-       } elsif ($opt eq 'disk') {
-           $conf->{'pve.disksize'} = $value;
-           push @nohotplug, $opt;
-           next if $running;
+           $conf->{$opt} = PVE::Tools::encode_text($value);
        } elsif ($opt =~ m/^net(\d+)$/) {
            my $netid = $1;
-           my $net = PVE::LXC::parse_lxc_network($value);
-           $net->{'veth.pair'} = "veth${vmid}.$netid";
-           if (!$running) {
-               $conf->{$opt} = $net;
+           my $net = parse_lxc_network($value);
+           if (!$running) {
+               $conf->{$opt} = print_lxc_network($net);
            } else {
                update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
            }
+       } elsif ($opt eq 'protection') {
+           $conf->{$opt} = $value ? 1 : 0;
+        } elsif ($opt =~ m/^mp(\d+)$/) {
+           $conf->{$opt} = $value;
+           push @$new_disks, $opt;
+           push @nohotplug, $opt;
+           next;
+        } elsif ($opt eq 'rootfs') {
+           die "implement me: $opt";
        } else {
            die "implement me: $opt";
        }
-       PVE::LXC::write_config($vmid, $conf) if $running;
+       write_config($vmid, $conf) if $running;
+    }
+
+    if ($running && scalar(@nohotplug)) {
+       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
+    }
+
+    if (@$new_disks) {
+       my $storage_cfg = PVE::Storage::config();
+       create_disks($storage_cfg, $vmid, $conf, $conf);
+       mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
+       umount_all($vmid, $storage_cfg, $conf, 0);
     }
+}
 
-    if ($running && scalar(@nohotplug)) {
-       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
+sub has_dev_console {
+    my ($conf) = @_;
+
+    return !(defined($conf->{console}) && !$conf->{console});
+}
+       
+sub get_tty_count {
+    my ($conf) = @_;
+
+    return $conf->{tty} // $confdesc->{tty}->{default};
+}
+
+sub get_cmode {
+    my ($conf) = @_;
+
+    return $conf->{cmode} // $confdesc->{cmode}->{default};
+}
+
+sub get_console_command {
+    my ($vmid, $conf) = @_;
+
+    my $cmode = get_cmode($conf);
+
+    if ($cmode eq 'console') {
+       return ['lxc-console', '-n',  $vmid, '-t', 0];
+    } elsif ($cmode eq 'tty') {
+       return ['lxc-console', '-n',  $vmid];
+    } elsif ($cmode eq 'shell') {
+       return ['lxc-attach', '--clear-env', '-n', $vmid];
+    } else {
+       die "internal error";
     }
 }
 
@@ -1213,34 +1269,59 @@ sub get_primary_ips {
 
     # return data from net0
 
-    my $net = $conf->{net0};
-    return undef if !$net;
+    return undef if !defined($conf->{net0});
+    my $net = parse_lxc_network($conf->{net0});
 
     my $ipv4 = $net->{ip};
-    $ipv4 =~ s!/\d+$!! if $ipv4;
-    my $ipv6 = $net->{ip};
-    $ipv6 =~ s!/\d+$!! if $ipv6;
+    if ($ipv4) {
+       if ($ipv4 =~ /^(dhcp|manual)$/) {
+           $ipv4 = undef
+       } else {
+           $ipv4 =~ s!/\d+$!!;
+       }
+    }
+    my $ipv6 = $net->{ip6};
+    if ($ipv6) {
+       if ($ipv6 =~ /^(dhcp|manual)$/) {
+           $ipv6 = undef;
+       } else {
+           $ipv6 =~ s!/\d+$!!;
+       }
+    }
 
     return ($ipv4, $ipv6);
 }
 
-sub destory_lxc_container {
-    my ($storage_cfg, $vmid, $conf) = @_;
 
-    if (my $volid = $conf->{'pve.volid'}) {
+sub destroy_lxc_container {
+    my ($storage_cfg, $vmid, $conf) = @_;
 
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
-       die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
+       PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
+    });
 
-       PVE::Storage::vdisk_free($storage_cfg, $volid);
+    rmdir "/var/lib/lxc/$vmid/rootfs";
+    unlink "/var/lib/lxc/$vmid/config";
+    rmdir "/var/lib/lxc/$vmid";
+    destroy_config($vmid);
 
-       destroy_config($vmid);
+    #my $cmd = ['lxc-destroy', '-n', $vmid ];
+    #PVE::Tools::run_command($cmd);
+}
 
-    } else {
-       my $cmd = ['lxc-destroy', '-n', $vmid ];
+sub vm_stop_cleanup {
+    my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
+    
+    eval {
+       if (!$keepActive) {
 
-       PVE::Tools::run_command($cmd);
-    }
+            my $vollist = get_vm_volumes($conf);
+           PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
+       }
+    };
+    warn $@ if $@; # avoid errors - just warn
 }
 
 my $safe_num_ne = sub {
@@ -1266,49 +1347,57 @@ my $safe_string_ne = sub {
 sub update_net {
     my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
 
-    my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth . "p";
+    if ($newnet->{type} ne 'veth') {
+       # for when there are physical interfaces
+       die "cannot update interface of type $newnet->{type}";
+    }
+
+    my $veth = "veth${vmid}i${netid}";
     my $eth = $newnet->{name};
 
-    if ($conf->{$opt}) {
-       if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
-           &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
+    if (my $oldnetcfg = $conf->{$opt}) {
+       my $oldnet = parse_lxc_network($oldnetcfg);
+
+       if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
+           &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
 
-            PVE::Network::veth_delete($veth);
+           PVE::Network::veth_delete($veth);
            delete $conf->{$opt};
-           PVE::LXC::write_config($vmid, $conf);
+           write_config($vmid, $conf);
 
-           hotplug_net($vmid, $conf, $opt, $newnet);
+           hotplug_net($vmid, $conf, $opt, $newnet, $netid);
 
-       } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
-                &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
-                &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
+       } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+                &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
+                &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
 
-               if ($conf->{$opt}->{bridge}){
+               if ($oldnet->{bridge}) {
                    PVE::Network::tap_unplug($veth);
-                   delete $conf->{$opt}->{bridge};
-                   delete $conf->{$opt}->{tag};
-                   delete $conf->{$opt}->{firewall};
-                   PVE::LXC::write_config($vmid, $conf);
+                   foreach (qw(bridge tag firewall)) {
+                       delete $oldnet->{$_};
+                   }
+                   $conf->{$opt} = print_lxc_network($oldnet);
+                   write_config($vmid, $conf);
                }
 
-                PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
-               $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
-               $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
-               $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
-               PVE::LXC::write_config($vmid, $conf);
+               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+               foreach (qw(bridge tag firewall)) {
+                   $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
+               }
+               $conf->{$opt} = print_lxc_network($oldnet);
+               write_config($vmid, $conf);
        }
     } else {
-       hotplug_net($vmid, $conf, $opt, $newnet);
+       hotplug_net($vmid, $conf, $opt, $newnet, $netid);
     }
 
     update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
 }
 
 sub hotplug_net {
-    my ($vmid, $conf, $opt, $newnet) = @_;
+    my ($vmid, $conf, $opt, $newnet, $netid) = @_;
 
-    my $veth = $newnet->{'veth.pair'};
+    my $veth = "veth${vmid}i${netid}";
     my $vethpeer = $veth . "p";
     my $eth = $newnet->{name};
 
@@ -1323,34 +1412,28 @@ sub hotplug_net {
     $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up'  ];
     PVE::Tools::run_command($cmd);
 
-    $conf->{$opt}->{type} = 'veth';
-    $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
-    $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
-    $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
-    $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
-    $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
-    $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
-
-    delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
-    delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
-    delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
-    delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
+    my $done = { type => 'veth' };
+    foreach (qw(bridge tag firewall hwaddr name)) {
+       $done->{$_} = $newnet->{$_} if $newnet->{$_};
+    }
+    $conf->{$opt} = print_lxc_network($done);
 
-    PVE::LXC::write_config($vmid, $conf);
+    write_config($vmid, $conf);
 }
 
 sub update_ipconfig {
     my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
 
-    my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
+    my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
 
-    my $optdata = $conf->{$opt};
+    my $optdata = parse_lxc_network($conf->{$opt});
     my $deleted = [];
     my $added = [];
-    my $netcmd = sub {
-       my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
-       PVE::Tools::run_command($cmd);
+    my $nscmd = sub {
+       my $cmdargs = shift;
+       PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
     };
+    my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
 
     my $change_ip_config = sub {
        my ($ipversion) = @_;
@@ -1360,14 +1443,18 @@ sub update_ipconfig {
        my $gw= "gw$suffix";
        my $ip= "ip$suffix";
 
-       my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
-       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
+       my $newip = $newnet->{$ip};
+       my $newgw = $newnet->{$gw};
+       my $oldip = $optdata->{$ip};
+
+       my $change_ip = &$safe_string_ne($oldip, $newip);
+       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
 
        return if !$change_ip && !$change_gw;
 
        # step 1: add new IP, if this fails we cancel
-       if ($change_ip && $newnet->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
+       if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
+           eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
            if (my $err = $@) {
                warn $err;
                return;
@@ -1380,31 +1467,45 @@ sub update_ipconfig {
        #   errors. The config is then saved.
        # Note: 'ip route replace' can add
        if ($change_gw) {
-           if ($newnet->{$gw}) {
-               eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
+           if ($newgw) {
+               eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
                if (my $err = $@) {
                    warn $err;
                    # the route was not replaced, the old IP is still available
                    # rollback (delete new IP) and cancel
                    if ($change_ip) {
-                       eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
+                       eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
                        warn $@ if $@; # no need to die here
                    }
                    return;
                }
            } else {
-               eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
+               eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
                # if the route was not deleted, the guest might have deleted it manually
                # warn and continue
                warn $@ if $@;
            }
        }
 
-       # from this point on we safe the configuration
+       # from this point on we save the configuration
        # step 3: delete old IP ignoring errors
-       if ($change_ip && $optdata->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
+       if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
+           # We need to enable promote_secondaries, otherwise our newly added
+           # address will be removed along with the old one.
+           my $promote = 0;
+           eval {
+               if ($ipversion == 4) {
+                   &$nscmd({ outfunc => sub { $promote = int(shift) } },
+                           'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
+                   &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
+               }
+               &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
+           };
            warn $@ if $@; # no need to die here
+
+           if ($ipversion == 4) {
+               &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
+           }
        }
 
        foreach my $property ($ip, $gw) {
@@ -1414,7 +1515,8 @@ sub update_ipconfig {
                delete $optdata->{$property};
            }
        }
-       PVE::LXC::write_config($vmid, $conf);
+       $conf->{$opt} = print_lxc_network($optdata);
+       write_config($vmid, $conf);
        $lxc_setup->setup_network($conf);
     };
 
@@ -1435,11 +1537,12 @@ my $snapshot_copy_config = sub {
 
     foreach my $k (keys %$source) {
        next if $k eq 'snapshots';
-       next if $k eq 'pve.snapstate';
-       next if $k eq 'pve.snaptime';
-       next if $k eq 'pve.lock';
+       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 'pve.comment';
+       next if $k eq 'description';
 
        $dest->{$k} = $source->{$k};
     }
@@ -1454,9 +1557,12 @@ my $snapshot_prepare = 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->{'pve.lock'} = 'snapshot';
+       $conf->{lock} = 'snapshot';
 
        die "snapshot name '$snapname' already used\n"
            if defined($conf->{snapshots}->{$snapname});
@@ -1468,13 +1574,12 @@ my $snapshot_prepare = sub {
 
        &$snapshot_copy_config($conf, $snap);
 
-       $snap->{'pve.snapstate'} = "prepare";
-       $snap->{'pve.snaptime'} = time();
-       $snap->{'pve.snapname'} = $snapname;
-       $snap->{'pve.snapcomment'} = $comment if $comment;
+       $snap->{'snapstate'} = "prepare";
+       $snap->{'snaptime'} = time();
+       $snap->{'description'} = $comment if $comment;
        $conf->{snapshots}->{$snapname} = $snap;
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
@@ -1490,20 +1595,20 @@ my $snapshot_commit = sub {
        my $conf = load_config($vmid);
 
        die "missing snapshot lock\n"
-           if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
+           if !($conf->{lock} && $conf->{lock} eq 'snapshot');
 
-       die "snapshot '$snapname' does not exist\n" 
+       die "snapshot '$snapname' does not exist\n"
            if !defined($conf->{snapshots}->{$snapname});
 
        die "wrong snapshot state\n"
-           if !($conf->{snapshots}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots}->{$snapname}->{'pve.snapstate'} eq "prepare");
-
-       delete $conf->{snapshots}->{$snapname}->{'pve.snapstate'};
-       delete $conf->{'pve.lock'};
-       $conf->{'pve.parent'} = $snapname;
+           if !($conf->{snapshots}->{$snapname}->{'snapstate'} && 
+                $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
 
-       PVE::LXC::write_config($vmid, $conf);
+       delete $conf->{snapshots}->{$snapname}->{'snapstate'};
+       delete $conf->{lock};
+       $conf->{parent} = $snapname;
 
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10 ,$updatefn);
@@ -1511,10 +1616,20 @@ my $snapshot_commit = sub {
 
 sub has_feature {
     my ($feature, $conf, $storecfg, $snapname) = @_;
-    #Fixme add other drives if necessary.
+    
     my $err;
-    my $volid = $conf->{'pve.volid'};
-    $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname);
+
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       return if $err; # skip further test
+       
+       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
+
+       # TODO: implement support for mountpoints
+       die "unable to handle mountpoint '$ms' - feature not implemented\n"
+           if $ms ne 'rootfs';
+    });
 
     return $err ? 0 : 1;
 }
@@ -1524,7 +1639,7 @@ sub snapshot_create {
 
     my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
 
-    my $config = load_config($vmid);
+    my $conf = load_config($vmid);
 
     my $cmd = "/usr/bin/lxc-freeze -n $vmid";
     my $running = check_running($vmid);
@@ -1534,7 +1649,8 @@ sub snapshot_create {
        };
 
        my $storecfg = PVE::Storage::config();
-       my $volid = $config->{'pve.volid'};
+       my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
+       my $volid = $rootinfo->{volume};
 
        $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
        if ($running) {
@@ -1561,15 +1677,18 @@ sub snapshot_delete {
 
        $conf = load_config($vmid);
 
+       die "you can't delete a snapshot if vm is a template\n"
+           if is_template($conf);
+
        $snap = $conf->{snapshots}->{$snapname};
 
        check_lock($conf);
 
        die "snapshot '$snapname' does not exist\n" if !defined($snap);
 
-       $snap->{'pve.snapstate'} = 'delete';
+       $snap->{snapstate} = 'delete';
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
@@ -1580,20 +1699,22 @@ sub snapshot_delete {
 
        check_lock($conf);
 
-       if ($conf->{'pve.parent'} eq $snapname) {
-           if ($conf->{snapshots}->{$snapname}->{'pve.snapname'}) {
-               $conf->{'pve.parent'} = $conf->{snapshots}->{$snapname}->{'pve.parent'};
+       if ($conf->{parent} eq $snapname) {
+           if ($conf->{snapshots}->{$snapname}->{snapname}) {
+               $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
            } else {
-               delete $conf->{'pve.parent'};
+               delete $conf->{parent};
            }
        }
 
        delete $conf->{snapshots}->{$snapname};
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
-    my $volid = $conf->{snapshots}->{$snapname}->{'pve.volid'};
+    my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
+    my $rootinfo = parse_ct_mountpoint($rootfs);
+    my $volid = $rootinfo->{volume};
 
     eval {
        PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
@@ -1611,6 +1732,481 @@ sub snapshot_delete {
 sub snapshot_rollback {
     my ($vmid, $snapname) = @_;
 
-    die "Not implemented\n";
+    my $storecfg = PVE::Storage::config();
+
+    my $conf = load_config($vmid);
+
+    die "you can't rollback if vm is a template\n" if is_template($conf);
+
+    my $snap = $conf->{snapshots}->{$snapname};
+
+    die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+    my $rootfs = $snap->{rootfs};
+    my $rootinfo = parse_ct_mountpoint($rootfs);
+    my $volid = $rootinfo->{volume};
+
+    PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
+
+    my $updatefn = sub {
+
+       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" 
+           if $snap->{snapstate};
+
+       check_lock($conf);
+
+       system("lxc-stop -n $vmid --kill") if check_running($vmid);
+
+       die "unable to rollback vm $vmid: vm is running\n"
+           if check_running($vmid);
+
+       $conf->{lock} = 'rollback';
+
+       my $forcemachine;
+
+       # copy snapshot config to current config
+
+       my $tmp_conf = $conf;
+       &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
+       $conf->{snapshots} = $tmp_conf->{snapshots};
+       delete $conf->{snaptime};
+       delete $conf->{snapname};
+       $conf->{parent} = $snapname;
+
+       write_config($vmid, $conf);
+    };
+
+    my $unlockfn = sub {
+       delete $conf->{lock};
+       write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
+
+    lock_container($vmid, 5, $unlockfn);
+}
+
+sub template_create {
+    my ($vmid, $conf) = @_;
+
+    my $storecfg = PVE::Storage::config();
+
+    my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
+    my $volid = $rootinfo->{volume};
+
+    die "Template feature is not available for '$volid'\n"
+       if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
+
+    PVE::Storage::activate_volumes($storecfg, [$volid]);
+
+    my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
+    $rootinfo->{volume} = $template_volid;
+    $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
+
+    write_config($vmid, $conf);
+}
+
+sub is_template {
+    my ($conf) = @_;
+
+    return 1 if defined $conf->{template} && $conf->{template} == 1;
+}
+
+sub mountpoint_names {
+    my ($reverse) = @_;
+
+    my @names = ('rootfs');
+
+    for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+       push @names, "mp$i";
+    }
+
+    return $reverse ? reverse @names : @names;
+}
+
+sub foreach_mountpoint_full {
+    my ($conf, $reverse, $func) = @_;
+
+    foreach my $key (mountpoint_names($reverse)) {
+       my $value = $conf->{$key};
+       next if !defined($value);
+       my $mountpoint = parse_ct_mountpoint($value);
+       $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
+       &$func($key, $mountpoint);
+    }
+}
+
+sub foreach_mountpoint {
+    my ($conf, $func) = @_;
+
+    foreach_mountpoint_full($conf, 0, $func);
+}
+
+sub foreach_mountpoint_reverse {
+    my ($conf, $func) = @_;
+
+    foreach_mountpoint_full($conf, 1, $func);
+}
+
+sub check_ct_modify_config_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
+
+    return 1 if $authuser ne 'root@pam';
+
+    foreach my $opt (@$key_list) {
+
+       if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
+       } elsif ($opt eq 'disk') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
+       } elsif ($opt eq 'memory' || $opt eq 'swap') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
+       } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
+                $opt eq 'searchdomain' || $opt eq 'hostname') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
+       } else {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
+       }
+    }
+
+    return 1;
+}
+
+sub umount_all {
+    my ($vmid, $storage_cfg, $conf, $noerr) = @_;
+
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    my $volid_list = get_vm_volumes($conf);
+
+    foreach_mountpoint_reverse($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       my $volid = $mountpoint->{volume};
+       my $mount = $mountpoint->{mp};
+
+       return if !$volid || !$mount;
+
+       my $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
+
+       return if !PVE::ProcFSTools::is_mounted($mount_path);
+
+       eval {
+           PVE::Tools::run_command(['umount', '-d', $mount_path]);
+       };
+       if (my $err = $@) {
+           if ($noerr) {
+               warn $err;
+           } else {
+               die $err;
+           }
+       }
+    });
+}
+
+sub mount_all {
+    my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
+
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    File::Path::make_path($rootdir);
+
+    my $volid_list = get_vm_volumes($conf);
+    PVE::Storage::activate_volumes($storage_cfg, $volid_list);
+
+    eval {
+       foreach_mountpoint($conf, sub {
+           my ($ms, $mountpoint) = @_;
+
+           my $volid = $mountpoint->{volume};
+           my $mount = $mountpoint->{mp};
+
+           return if !$volid || !$mount;
+
+           my $image_path = PVE::Storage::path($storage_cfg, $volid);
+           my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+               PVE::Storage::parse_volname($storage_cfg, $volid);
+
+           die "unable to mount base volume - internal error" if $isBase;
+
+           File::Path::make_path "$rootdir/$mount" if $mkdirs;
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+        });
+    };
+    if (my $err = $@) {
+       warn "mounting container failed - $err";
+       umount_all($vmid, $storage_cfg, $conf, 1);
+    }
+
+    return $rootdir;
+}
+
+
+sub mountpoint_mount_path {
+    my ($mountpoint, $storage_cfg, $snapname) = @_;
+
+    return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
+}
+
+# use $rootdir = undef to just return the corresponding mount path
+sub mountpoint_mount {
+    my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
+
+    my $volid = $mountpoint->{volume};
+    my $mount = $mountpoint->{mp};
+    
+    return if !$volid || !$mount;
+
+    my $mount_path;
+    
+    if (defined($rootdir)) {
+       $rootdir =~ s!/+$!!;
+       $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
+       File::Path::mkpath($mount_path);
+    }
+    
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+    die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
+
+    if ($storage) {
+
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
+       my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
+       return $path if !$mount_path;
+
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
+
+       if ($format eq 'subvol') {
+           if ($snapname) {
+               if ($scfg->{type} eq 'zfspool') {
+                   my $path_arg = $path;
+                   $path_arg =~ s!^/+!!;
+                   PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+               } else {
+                   die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
+               }               
+           } else {
+               PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
+           }
+           return $path;
+       } elsif ($format eq 'raw') {
+           my @extra_opts;
+           if ($scfg->{path}) {
+               push @extra_opts, '-o', 'loop';
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
+               # do nothing
+           } else {
+               die "unsupported storage type '$scfg->{type}'\n";
+           }
+           if ($isBase || defined($snapname)) {
+               PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
+           } else {
+               PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+           }
+           return $path;
+       } else {
+           die "unsupported image format '$format'\n";
+       }
+    } elsif ($volid =~ m|^/dev/.+|) {
+       PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
+       return $volid;
+    } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
+       PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
+       return $volid;
+    }
+    
+    die "unsupported storage";
+}
+
+sub get_vm_volumes {
+    my ($conf, $excludes) = @_;
+
+    my $vollist = [];
+
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       return if $excludes && $ms eq $excludes;
+
+       my $volid = $mountpoint->{volume};
+
+        return if !$volid || $volid =~ m|^/|;
+
+        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+        return if !$sid;
+
+        push @$vollist, $volid;
+    });
+
+    return $vollist;
+}
+
+sub mkfs {
+    my ($dev) = @_;
+
+    PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
+}
+
+sub format_disk {
+    my ($storage_cfg, $volid) = @_;
+
+    if ($volid =~ m!^/dev/.+!) {
+       mkfs($volid);
+       return;
+    }
+
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+    die "cannot format volume '$volid' with no storage\n" if !$storage;
+
+    my $path = PVE::Storage::path($storage_cfg, $volid);
+
+    my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+       PVE::Storage::parse_volname($storage_cfg, $volid);
+
+    die "cannot format volume '$volid' (format == $format)\n"
+       if $format ne 'raw';
+
+    mkfs($path);
+}
+
+sub destroy_disks {
+    my ($storecfg, $vollist) = @_;
+
+    foreach my $volid (@$vollist) {
+       eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+       warn $@ if $@;
+    }
+}
+
+sub create_disks {
+    my ($storecfg, $vmid, $settings, $conf) = @_;
+
+    my $vollist = [];
+
+    eval {
+       foreach_mountpoint($settings, sub {
+           my ($ms, $mountpoint) = @_;
+
+           my $volid = $mountpoint->{volume};
+           my $mp = $mountpoint->{mp};
+
+           my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+           return if !$storage;
+
+           if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
+               my ($storeid, $size_gb) = ($1, $2);
+
+               my $size_kb = int(${size_gb}*1024) * 1024;
+
+               my $scfg = PVE::Storage::storage_config($storecfg, $storage);
+               # fixme: use better naming ct-$vmid-disk-X.raw?
+
+               if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+                   if ($size_kb > 0) {
+                       $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
+                                                          undef, $size_kb);
+                       format_disk($storecfg, $volid);
+                   } else {
+                       $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                          undef, 0);
+                   }
+               } elsif ($scfg->{type} eq 'zfspool') {
+
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                      undef, $size_kb);
+               } elsif ($scfg->{type} eq 'drbd') {
+
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+                   format_disk($storecfg, $volid);
+
+               } elsif ($scfg->{type} eq 'rbd') {
+
+                   die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+                   format_disk($storecfg, $volid);
+               } else {
+                   die "unable to create containers on storage type '$scfg->{type}'\n";
+               }
+               push @$vollist, $volid;
+                my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
+               $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
+           } else {
+               # use specified/existing volid
+           }
+       });
+    };
+    # free allocated images on error
+    if (my $err = $@) {
+       destroy_disks($storecfg, $vollist);
+        die $err;
+    }
+    return $vollist;
+}
+
+# bash completion helper
+
+sub complete_os_templates {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Storage::config();
+
+    my $storeid;
+
+    if ($cvalue =~ m/^([^:]+):/) {
+       $storeid = $1;
+    }
+
+    my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
+    my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
+
+    my $res = [];
+    foreach my $id (keys %$data) {
+       foreach my $item (@{$data->{$id}}) {
+           push @$res, $item->{volid} if defined($item->{volid});
+       }
+    }
+
+    return $res;
+}
+
+my $complete_ctid_full = sub {
+    my ($running) = @_;
+
+    my $idlist = vmstatus();
+
+    my $active_hash = list_active_containers();
+
+    my $res = [];
+
+    foreach my $id (keys %$idlist) {
+       my $d = $idlist->{$id};
+       if (defined($running)) {
+           next if $d->{template};
+           next if $running && !$active_hash->{$id};
+           next if !$running && $active_hash->{$id};
+       }
+       push @$res, $id;
+
+    }
+    return $res;
+};
+
+sub complete_ctid {
+    return &$complete_ctid_full();
+}
+
+sub complete_ctid_stopped {
+    return &$complete_ctid_full(0);
+}
+
+sub complete_ctid_running {
+    return &$complete_ctid_full(1);
 }
+
 1;