]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
disk formatting and mounting changed
[pve-container.git] / src / PVE / LXC.pm
index b8ac53601e6673a988d382c1d6a674d8325f2dbe..c54b491f3861411080d3f61a4448bab8a3258086 100644 (file)
@@ -12,12 +12,15 @@ 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 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,384 +33,379 @@ 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,
+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.id_map' => 1,
+PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
+    description => "The name of the snapshot.",
+    type => 'string', format => 'pve-configid',
+    maxLength => 40,
+});
 
-    '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+',
-
-    # 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',
+    },
+};
 
-    # 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 $network_counter = 0;
-    my $network_list = [];
-    my $host_ifnames = {};
-    my $snapname;
-    my $network;
-
-    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;
-           }
-       }
+    my $conf = $res;
+    my $descr = '';
+    my $section = '';
 
-       die "unable to find free host_ifname"; # should not happen
-    };
+    my @lines = split(/\n/, $raw);
+    foreach my $line (@lines) {
+       next if $line =~ m/^\s*$/;
 
-    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";
-           }
+       if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
+           $section = $1;
+           $conf->{description} = $descr if $descr;
+           $descr = '';
+           $conf = $res->{snapshots}->{$section} = {};
+           next;
        }
-    };
 
-    my $finalize_section = sub {
-       &$push_network($network); # flush
-       
-       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 ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{"net$1"} = $net;
-               } else {
-                   $data->{"net$1"} = $net;
-               }
-           }
+       if ($line =~ m/^\#(.*)\s*$/) {
+           $descr .= PVE::Tools::decode_text($1) . "\n";
+           next;
        }
 
-       # reset helper vars
-       $network_counter = 0;
-       $network_list = [];
-       $host_ifnames = {};
-       $network = undef;
-    };
-    
-    while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
-       my $line = $1;
-       next if $line =~ m/^\s*$/; # skip empty lines
-       next if $line =~ m/^#/; # skip comments
-
-       # snap.pve.snapname starts new sections
-       if ($line =~ m/^(snap\.)?pve\.snapname\s*=\s*(\w*)\s*$/) {
-           my $value = $2;
-           
-           &$finalize_section();
-
-           $snapname = $value;
-           $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
-           
-       } elsif ($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";
-           }
-       } elsif ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-           my ($subkey, $value) = ($2, $3);
-           if ($valid_pve_network_keys->{$subkey}) {
-               $network->{$subkey} = $value;
+       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 {
-               die "unable to parse config line: $line\n";
-           }
-       } elsif ($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);
-               }
+               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 {
-           die "unable to parse config line: $line\n";
+           warn "vm $vmid - unable to parse config: $line\n";
        }
     }
 
-    &$finalize_section();
+    $conf->{description} = $descr if $descr;
+
+    delete $res->{snapstate}; # just to be sure
 
-    return $data;
+    return $res;
 }
 
 sub config_list {
@@ -430,7 +428,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 {
@@ -441,9 +439,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);
@@ -457,17 +456,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 {
@@ -478,20 +473,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.
 
@@ -501,7 +482,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 {
@@ -536,10 +517,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); };
@@ -578,88 +559,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) = @_;
 
@@ -671,6 +570,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};
     }
 
@@ -692,7 +605,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;
            }
        }
@@ -759,40 +672,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'};
+       $d->{cpus} = $conf->{cpulimit} // 0;
 
-       if ($cfs_period_us && $cfs_quota_us) {
-           $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
-       }
-
-       $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;
@@ -802,6 +705,8 @@ sub vmstatus {
 
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
+
+       $d->{template} = is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
@@ -826,6 +731,70 @@ 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);
+};
+
+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) = @_;
+
+    my $opts = '';
+
+    die "missing volume\n" if !$info->{volume};
+
+    foreach my $o ('size', 'backup') {
+       $opts .= ",$o=$info->{$o}" if defined($info->{$o});
+    }
+
+    return "$info->{volume}$opts";
+}
 
 sub print_lxc_network {
     my $net = shift;
@@ -975,75 +944,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 ' ')
@@ -1071,7 +1067,7 @@ sub verify_searchdomain_list {
     return join(' ', @list);
 }
 
-sub update_lxc_config {
+sub update_pct_config {
     my ($vmid, $conf, $running, $param, $delete) = @_;
 
     my @nohotplug;
@@ -1084,97 +1080,97 @@ 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 'rootfs' || $opt =~ m/^mp(\d+)$/) {
+               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 'rootfs' || $opt =~ m/^mp(\d+)$/) {
+           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)) {
@@ -1182,13 +1178,47 @@ sub update_lxc_config {
     }
 }
 
+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";
+    }
+}
+
 sub get_primary_ips {
     my ($conf) = @_;
 
     # 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};
     if ($ipv4) {
@@ -1210,23 +1240,39 @@ sub get_primary_ips {
     return ($ipv4, $ipv6);
 }
 
-sub destory_lxc_container {
+
+sub destroy_lxc_container {
     my ($storage_cfg, $vmid, $conf) = @_;
 
-    if (my $volid = $conf->{'pve.volid'}) {
+    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;
+    });
 
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
-       die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
+    rmdir "/var/lib/lxc/$vmid/rootfs";
+    unlink "/var/lib/lxc/$vmid/config";
+    rmdir "/var/lib/lxc/$vmid";
+    destroy_config($vmid);
 
-       PVE::Storage::vdisk_free($storage_cfg, $volid);
+    #my $cmd = ['lxc-destroy', '-n', $vmid ];
+    #PVE::Tools::run_command($cmd);
+}
 
-       destroy_config($vmid);
+sub vm_stop_cleanup {
+    my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
+    
+    eval {
+       if (!$keepActive) {
 
-    } else {
-       my $cmd = ['lxc-destroy', '-n', $vmid ];
+            my $vollist = get_vm_volumes($conf);
+            my $loopdevlist = get_vm_volumes($conf, 'rootfs');
 
-       PVE::Tools::run_command($cmd);
-    }
+           detach_loops($storage_cfg, $loopdevlist);
+           PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
+       }
+    };
+    warn $@ if $@; # avoid errors - just warn
 }
 
 my $safe_num_ne = sub {
@@ -1252,49 +1298,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);
 
-            PVE::Network::veth_delete($veth);
+       if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
+           &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
+
+           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};
 
@@ -1309,34 +1363,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) = @_;
@@ -1346,14 +1394,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;
@@ -1366,31 +1418,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) {
@@ -1400,7 +1466,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);
     };
 
@@ -1421,11 +1488,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};
     }
@@ -1440,9 +1508,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});
@@ -1454,13 +1525,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);
@@ -1476,20 +1546,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");
+           if !($conf->{snapshots}->{$snapname}->{'snapstate'} && 
+                $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
 
-       delete $conf->{snapshots}->{$snapname}->{'pve.snapstate'};
-       delete $conf->{'pve.lock'};
-       $conf->{'pve.parent'} = $snapname;
-
-       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);
@@ -1497,10 +1567,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;
 }
@@ -1510,7 +1590,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);
@@ -1520,7 +1600,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) {
@@ -1547,15 +1628,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);
@@ -1566,20 +1650,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);
@@ -1601,16 +1687,22 @@ sub snapshot_rollback {
 
     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);
 
-    PVE::Storage::volume_rollback_is_possible($storecfg, $snap->{'pve.volid'},
-                                             $snapname);
+    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};
+       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" 
+           if $snap->{snapstate};
 
        check_lock($conf);
 
@@ -1619,7 +1711,7 @@ sub snapshot_rollback {
        die "unable to rollback vm $vmid: vm is running\n"
            if check_running($vmid);
 
-       $conf->{'pve.lock'} = 'rollback';
+       $conf->{lock} = 'rollback';
 
        my $forcemachine;
 
@@ -1628,23 +1720,451 @@ sub snapshot_rollback {
        my $tmp_conf = $conf;
        &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
        $conf->{snapshots} = $tmp_conf->{snapshots};
-       delete $conf->{'pve.snaptime'};
-       delete $conf->{'pve.snapname'};
-       $conf->{'pve.parent'} = $snapname;
+       delete $conf->{snaptime};
+       delete $conf->{snapname};
+       $conf->{parent} = $snapname;
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     my $unlockfn = sub {
-       delete $conf->{'pve.lock'};
-       PVE::LXC::write_config($vmid, $conf);
+       delete $conf->{lock};
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
 
-    PVE::Storage::volume_snapshot_rollback($storecfg, $conf->{'pve.volid'}, $snapname);
+    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);
+
+    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 loopdevices_list {
+
+    my $loopdev = {};
+    my $parser = sub {
+       my $line = shift;
+       if ($line =~ m/^(\/dev\/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
+           $loopdev->{$1} = $2;
+       }
+    };
+
+    PVE::Tools::run_command(['losetup'], outfunc => $parser);
+
+    return $loopdev;
+}
+
+sub blockdevices_list {
+
+    my $bdevs = {};
+    dir_glob_foreach("/sys/dev/block/", '(\d+):(\d+)', sub {
+        my (undef, $major, $minor) = @_;
+        my $bdev = readlink("/sys/dev/block/$major:$minor");
+        $bdev =~ s/\.\.\/\.\.\/devices\/virtual\/block\//\/dev\//;
+        $bdevs->{$bdev}->{major} = $major;
+        $bdevs->{$bdev}->{minor} = $minor;
+    });
+    return $bdevs;
+}
+
+sub find_loopdev {
+    my ($loopdevs, $path) = @_;
+
+    foreach my $dev (keys %$loopdevs){
+       return $dev if $loopdevs->{$dev} eq $path;
+    }
+}
+
+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 attach_loops {
+    my ($storage_cfg, $vollist, $snapname) = @_;
+
+    my $loopdevs = {};
+
+    foreach my $volid (@$vollist) {
+
+       my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
+
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
+
+       if ($format eq 'raw' && $scfg->{path}) {
+           my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
+           my $loopdev;
+
+           my $parser = sub {
+               my $line = shift;
+               $loopdev = $line if $line =~m|^/dev/loop\d+$|;
+               $loopdevs->{$loopdev} = $path;
+           };
+
+           PVE::Tools::run_command(['losetup', '--find', '--show', $path], outfunc => $parser);
+       }
+    }
+
+    return $loopdevs;
+}
+
+sub detach_loops {
+    my ($storage_cfg, $vollist, $snapname) = @_;
+
+    my $loopdevs = loopdevices_list();
+
+    foreach my $volid (@$vollist) {
+
+       my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
+
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
+
+       if ($format eq 'raw' && $scfg->{path}) {
+           my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
+            foreach my $dev (keys %$loopdevs){
+               PVE::Tools::run_command(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
+           }
+       }
+    }
+}
+
+sub umount_all {
+    my ($vmid, $storage_cfg, $conf, $noerr, $loopdevs) = @_;
+
+    $loopdevs ||= loopdevices_list();
+
+    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;
+
+       # fixme: test if mounted?
+       eval {
+           PVE::Tools::run_command(['umount', '-d', $mount_path]);
+       };
+       if (my $err = $@) {
+           if ($noerr) {
+               warn $err;
+           } else {
+               die $err;
+           }
+       }
+    });
+
+    PVE::LXC::detach_loops($storage_cfg, $volid_list);
+}
+
+sub mount_all {
+    my ($vmid, $storage_cfg, $conf) = @_;
+
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+
+    my $volid_list = get_vm_volumes($conf);
+    PVE::Storage::activate_volumes($storage_cfg, $volid_list);
+
+    my $loopdevs;
+    eval {
+       $loopdevs = attach_loops($storage_cfg, $volid_list);
+
+       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;
+
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg, $loopdevs);
+        });
+    };
+    if (my $err = $@) {
+       warn "mounting container failed - $err";
+       umount_all($vmid, $storage_cfg, $conf, 1);
+    }
+
+    return ($rootdir, $loopdevs) if wantarray;
+    return $rootdir;
+}
+
+
+sub mountpoint_mount_path {
+    my ($mountpoint, $storage_cfg, $loopdevs, $snapname) = @_;
+
+    return mountpoint_mount($mountpoint, undef, $storage_cfg, $loopdevs, $snapname);
+}
+
+# use $rootdir = undef to just return the corresponding mount path
+sub mountpoint_mount {
+    my ($mountpoint, $rootdir, $storage_cfg, $loopdevs, $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);
+
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
+
+       if ($format eq 'subvol') {
+           
+           if ($mount_path) {
+               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') {
+
+           if ($scfg->{path}) {
+               $path = find_loopdev($loopdevs, $path) if $loopdevs;
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
+               # do nothing
+           } else {
+               die "unsupported storage type '$scfg->{type}'\n";
+           }
+           if ($mount_path) {
+               if ($isBase || defined($snapname)) {
+                   PVE::Tools::run_command(['mount', '-o', 'ro', $path, $mount_path]);
+               } else {
+                   PVE::Tools::run_command(['mount', $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;
+}
+
+# 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;
+}
+
+sub complete_migration_target {
+
+    my $res = [];
+
+    my $nodelist = PVE::Cluster::get_nodelist();
+    foreach my $node (@$nodelist) {
+       next if $node eq $nodename;
+       push @$res, $node;
+    }
+
+    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;