]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
VZDump lock update, drop lock_aquire/lock_release
[pve-container.git] / src / PVE / LXC.pm
index 5cb09f3c78121210051559cd7873b0003e5c7e15..df8bb28d6561aaf2a47e9b06e0d4432d01eec93e 100644 (file)
@@ -5,6 +5,8 @@ use warnings;
 use POSIX qw(EINTR);
 
 use File::Path;
+use File::Spec;
+use Cwd qw();
 use Fcntl ':flock';
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file);
@@ -12,1414 +14,2472 @@ 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 Time::HiRes qw (gettimeofday);
 
 use Data::Dumper;
 
-cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
-
-PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
-sub verify_lxc_network {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_lxc_network($value);
+my $nodename = PVE::INotify::nodename();
 
-    return undef if $noerr;
+my $cpuinfo= PVE::ProcFSTools::read_cpuinfo();
 
-    die "unable to parse network setting\n";
-}
+our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
+                          '--xattrs',
+                          '--xattrs-include=user.*',
+                          '--xattrs-include=security.capability',
+                          '--warning=no-xattr-write' ];
 
-my $nodename = PVE::INotify::nodename();
+cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
 
-sub parse_lxc_size {
-    my ($name, $value) = @_;
+my $rootfs_desc = {
+    volume => {
+       type => 'string',
+       default_key => 1,
+       format => 'pve-lxc-mp-string',
+       format_description => 'volume',
+       description => 'Volume, device or directory to mount into the container.',
+    },
+    backup => {
+       type => 'boolean',
+       format_description => '[1|0]',
+       description => 'Whether to include the mountpoint in backups.',
+       optional => 1,
+    },
+    size => {
+       type => 'string',
+       format => 'disk-size',
+       format_description => 'DiskSize',
+       description => 'Volume size (read only value).',
+       optional => 1,
+    },
+    acl => {
+       type => 'boolean',
+       format_description => 'acl',
+       description => 'Explicitly enable or disable ACL support.',
+       optional => 1,
+    },
+    ro => {
+       type => 'boolean',
+       format_description => 'ro',
+       description => 'Read-only mountpoint (not supported with bind mounts)',
+       optional => 1,
+    },
+};
 
-    if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
-       my ($res, $unit) = ($1, lc($2 || 'b'));
+PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
+    type => 'string', format => $rootfs_desc,
+    description => "Use volume as container root.",
+    optional => 1,
+});
 
-       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';
-    }
+PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
+    description => "The name of the snapshot.",
+    type => 'string', format => 'pve-configid',
+    maxLength => 40,
+});
 
-    return undef;
-}
+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', 'fedora', 'opensuse', '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 a 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 the 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', format => 'dns-name',
+       maxLength => 255,
+    },
+    description => {
+       optional => 1,
+       type => 'string',
+       description => "Container description. Only used on the configuration web interface.",
+    },
+    searchdomain => {
+       optional => 1,
+       type => 'string', format => 'dns-name-list',
+       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
+    },
+    nameserver => {
+       optional => 1,
+       type => 'string', format => 'address-list',
+       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor 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 CT or CT's disk remove/update operation.",
+       default => 0,
+    },
+    unprivileged => {
+       optional => 1,
+       type => 'boolean',
+       description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
+       default => 0,
+    },
+};
 
-my $valid_lxc_keys = {
-    'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
+my $valid_lxc_conf_keys = {
     'lxc.include' => 1,
-    'lxc.rootfs' => 1,
-    'lxc.mount' => 1,
+    'lxc.arch' => 1,
     'lxc.utsname' => 1,
-
-    'lxc.id_map' => 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+',
-
-    # mount related
-    'lxc.mount' => 1,
-    'lxc.mount.entry' => 1,
-    'lxc.mount.auto' => 1,
-
-    # not used by pve
-    'lxc.tty' => '\d+',
-    'lxc.pts' => 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' => 'lxc.rootfs is auto generated from rootfs',
+    'lxc.rootfs.mount' => 1,
+    'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
+                            ', please use mountpoint options in the "rootfs" key',
+    # 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.stop' => 1,
     'lxc.hook.post-stop' => 1,
     'lxc.hook.clone' => 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,
+};
 
-    # pve related keys
-    'pve.nameserver' => sub {
-       my ($name, $value) = @_;
-       return verify_nameserver_list($value);
+my $netconf_desc = {
+    type => {
+       type => 'string',
+       optional => 1,
+       description => "Network interface type.",
+       enum => [qw(veth)],
     },
-    'pve.searchdomain' => sub {
-       my ($name, $value) = @_;
-       return verify_searchdomain_list($value);
+    name => {
+       type => 'string',
+       format_description => 'String',
+       description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
+       pattern => '[-_.\w\d]+',
     },
-    'pve.onboot' => '(0|1)',
-    'pve.startup' => sub {
-       my ($name, $value) = @_;
-       return PVE::JSONSchema::pve_verify_startup_order($value);
+    bridge => {
+       type => 'string',
+       format_description => 'vmbr<Number>',
+       description => 'Bridge to attach the network device to.',
+       pattern => '[-_.\w\d]+',
+       optional => 1,
     },
-    'pve.comment' => 1,
-    'pve.disksize' => '\d+(\.\d+)?',
-    'pve.volid' => sub {
-       my ($name, $value) = @_;
-       PVE::Storage::parse_volume_id($value);
-       return $value;
+    hwaddr => {
+       type => 'string',
+       format_description => 'MAC',
+       description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
+       pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
+       optional => 1,
+    },
+    mtu => {
+       type => 'integer',
+       format_description => 'Number',
+       description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
+       minimum => 64, # minimum ethernet frame is 64 bytes
+       optional => 1,
+    },
+    ip => {
+       type => 'string',
+       format => 'pve-ipv4-config',
+       format_description => 'IPv4Format/CIDR',
+       description => 'IPv4 address in CIDR format.',
+       optional => 1,
+    },
+    gw => {
+       type => 'string',
+       format => 'ipv4',
+       format_description => 'GatewayIPv4',
+       description => 'Default gateway for IPv4 traffic.',
+       optional => 1,
+    },
+    ip6 => {
+       type => 'string',
+       format => 'pve-ipv6-config',
+       format_description => 'IPv6Format/CIDR',
+       description => 'IPv6 address in CIDR format.',
+       optional => 1,
+    },
+    gw6 => {
+       type => 'string',
+       format => 'ipv6',
+       format_description => 'GatewayIPv6',
+       description => 'Default gateway for IPv6 traffic.',
+       optional => 1,
+    },
+    firewall => {
+       type => 'boolean',
+       format_description => '[1|0]',
+       description => "Controls whether this interface's firewall rules should be used.",
+       optional => 1,
+    },
+    tag => {
+       type => 'integer',
+       format_description => 'VlanNo',
+       minimum => '2',
+       maximum => '4094',
+       description => "VLAN tag for this interface.",
+       optional => 1,
+    },
+    trunks => {
+       type => 'string',
+       pattern => qr/\d+(?:;\d+)*/,
+       format_description => 'vlanid[;vlanid...]',
+       description => "VLAN ids to pass through the interface",
+       optional => 1,
     },
-
-     #pve snapshot
-    'pve.lock' => 1,
-    'pve.snaptime' => 1,
-    'pve.snapcomment' => 1,
-    'pve.parent' => 1,
-    'pve.snapstate' => 1,
-    'pve.snapname' => 1,
 };
+PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
 
-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 => $netconf_desc,
+       description => "Specifies network interfaces for the container.",
+    };
+}
+
+PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
+sub verify_lxc_mp_string{
+    my ($mp, $noerr) = @_;
+
+    # do not allow:
+    # /./ or /../ 
+    # /. or /.. at the end
+    # ../ at the beginning
+    
+    if($mp =~ m@/\.\.?/@ ||
+       $mp =~ m@/\.\.?$@ ||
+       $mp =~ m@^\.\./@){
+       return undef if $noerr;
+       die "$mp contains illegal character sequences\n";
+    }
+    return $mp;
+}
 
-my $valid_pve_network_keys = {
-    bridge => 1,
-    tag => 1,
-    firewall => 1,
-    ip => 1,
-    gw => 1,
-    ip6 => 1,
-    gw6 => 1,
+my $mp_desc = {
+    %$rootfs_desc,
+    mp => {
+       type => 'string',
+       format => 'pve-lxc-mp-string',
+       format_description => 'Path',
+       description => 'Path to the mountpoint as seen from inside the container.',
+    },
 };
+PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
 
-my $lxc_array_configs = {
-    'lxc.network' => 1,
-    'lxc.mount' => 1,
-    'lxc.include' => 1,
-    'lxc.id_map' => 1,
-    'lxc.cgroup.devices.deny' => 1,
+my $unuseddesc = {
+    optional => 1,
+    type => 'string', format => 'pve-volume-id',
+    description => "Reference to unused volumes.",
 };
 
-sub write_lxc_config {
-    my ($filename, $data) = @_;
+my $MAX_MOUNT_POINTS = 10;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"mp$i"} = {
+       optional => 1,
+       type => 'string', format => $mp_desc,
+       description => "Use volume as container mount point (experimental feature).",
+       optional => 1,
+    };
+}
 
-    my $raw = "";
+my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"unused$i"} = $unuseddesc;
+}
 
-    return $raw if !$data;
+sub write_pct_config {
+    my ($filename, $conf) = @_;
 
-    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";
-       }
-    };
+    delete $conf->{snapstate}; # just to be sure
 
-    my $config_writer = sub {
-       my ($elem, $snapshot) = @_;
+    my $generate_raw_config = sub {
+       my ($conf) = @_;
 
-       my $done_hash = { digest => 1};
+       my $raw = '';
 
-       if (defined(my $value = $elem->{'pve.snapname'})) {
-            &$dump_entry('pve.snapname', $value, $done_hash, $snapshot);
+       # 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";
        }
 
-       # 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);
+       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';
+           my $value = $conf->{$key};
+           die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
+           $raw .= "$key: $value\n";
        }
-       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 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) = @_;
+
+    die "unknown setting '$key'\n" if !$confdesc->{$key};
 
-    my $parser = $valid_lxc_keys->{$name};
+    my $type = $confdesc->{$key}->{type};
 
-    die "invalid key '$name'\n" if !defined($parser);
+    if (!defined($value)) {
+       die "got undefined value\n";
+    }
+
+    if ($value =~ m/[\n\r]/) {
+       die "property contains a line feed\n";
+    }
 
-    if ($parser eq '1') {
+    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 $conf = $res;
+    my $descr = '';
+    my $section = '';
 
-    my $sec = &$split_config($raw);
+    my @lines = split(/\n/, $raw);
+    foreach my $line (@lines) {
+       next if $line =~ m/^\s*$/;
 
-    foreach my  $sec_raw (@{$sec}){
-       next if $sec_raw eq '';
-       my $snapname = undef;
+       if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
+           $section = $1;
+           $conf->{description} = $descr if $descr;
+           $descr = '';
+           $conf = $res->{snapshots}->{$section} = {};
+           next;
+       }
 
-       my $network_counter = 0;
-       my $network_list = [];
-       my $host_ifnames = {};
+       if ($line =~ m/^\#(.*)\s*$/) {
+           $descr .= PVE::Tools::decode_text($1) . "\n";
+           next;
+       }
 
-       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;
-               }
+       if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
+           my $key = $1;
+           my $value = $3;
+           my $validity = $valid_lxc_conf_keys->{$key} || 0;
+           if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
+               push @{$conf->{lxc}}, [$key, $value];
+           } elsif (my $errmsg = $validity) {
+               warn "vm $vmid - $key: $errmsg\n";
+           } 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";
+       }
+    }
 
-           die "unable to find free host_ifname"; # should not happen
-       };
+    $conf->{description} = $descr if $descr;
 
-       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";
-               }
-           }
-       };
+    delete $res->{snapstate}; # just to be sure
 
-       my $network;
+    return $res;
+}
 
-       while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
-           my $line = $1;
+sub config_list {
+    my $vmlist = PVE::Cluster::get_vmlist();
+    my $res = {};
+    return $res if !$vmlist || !$vmlist->{ids};
+    my $ids = $vmlist->{ids};
 
-           next if $line =~ m/^\#/;
-           next if $line =~ m/^\s*$/;
+    foreach my $vmid (keys %$ids) {
+       next if !$vmid; # skip CT0
+       my $d = $ids->{$vmid};
+       next if !$d->{node} || $d->{node} ne $nodename;
+       next if !$d->{type} || $d->{type} ne 'lxc';
+       $res->{$vmid}->{type} = 'lxc';
+    }
+    return $res;
+}
 
-           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;
+sub cfs_config_path {
+    my ($vmid, $node) = @_;
+
+    $node = $nodename if !$node;
+    return "nodes/$node/lxc/$vmid.conf";
+}
+
+sub config_file {
+    my ($vmid, $node) = @_;
+
+    my $cfspath = cfs_config_path($vmid, $node);
+    return "/etc/pve/$cfspath";
+}
+
+sub load_config {
+    my ($vmid, $node) = @_;
+
+    $node = $nodename if !$node;
+    my $cfspath = cfs_config_path($vmid, $node);
+
+    my $conf = PVE::Cluster::cfs_read_file($cfspath);
+    die "container $vmid does not exist\n" if !defined($conf);
+
+    return $conf;
+}
+
+sub create_config {
+    my ($vmid, $conf) = @_;
+
+    my $dir = "/etc/pve/nodes/$nodename/lxc";
+    mkdir $dir;
+
+    write_config($vmid, $conf);
+}
+
+sub destroy_config {
+    my ($vmid) = @_;
+
+    unlink config_file($vmid, $nodename);
+}
+
+sub write_config {
+    my ($vmid, $conf) = @_;
+
+    my $cfspath = cfs_config_path($vmid);
+
+    PVE::Cluster::cfs_write_file($cfspath, $conf);
+}
+
+# flock: we use one file handle per process, so lock file
+# can be called multiple times and will succeed for the same process.
+
+my $lock_handles =  {};
+my $lockdir = "/run/lock/lxc";
+
+sub lock_filename {
+    my ($vmid) = @_;
+
+    return "$lockdir/pve-config-${vmid}.lock";
+}
+
+sub lock_container {
+    my ($vmid, $timeout, $code, @param) = @_;
+
+    $timeout = 10 if !$timeout;
+
+    my $filename = lock_filename($vmid);
+
+    mkdir $lockdir if !-d $lockdir;
+
+    my $res = PVE::Tools::lock_file_full($filename, $timeout, 0, $code, @param);
+
+    die $@ if $@;
+
+    return $res;
+}
+
+sub option_exists {
+    my ($name) = @_;
+
+    return defined($confdesc->{$name});
+}
+
+# add JSON properties for create and set function
+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};
+    }
+
+    return $prop;
+}
+
+# container status helpers
+
+sub list_active_containers {
+
+    my $filename = "/proc/net/unix";
+
+    # similar test is used by lcxcontainers.c: list_active_containers
+    my $res = {};
+
+    my $fh = IO::File->new ($filename, "r");
+    return $res if !$fh;
+
+    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!^@/var/lib/lxc/(\d+)/command$!) {
+               $res->{$1} = 1;
            }
-           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;
+       }
+    }
+
+    close($fh);
+
+    return $res;
+}
+
+# warning: this is slow
+sub check_running {
+    my ($vmid) = @_;
+
+    my $active_hash = list_active_containers();
+
+    return 1 if defined($active_hash->{$vmid});
+
+    return undef;
+}
+
+sub get_container_disk_usage {
+    my ($vmid, $pid) = @_;
+
+    return PVE::Tools::df("/proc/$pid/root/", 1);
+}
+
+my $last_proc_vmid_stat;
+
+my $parse_cpuacct_stat = sub {
+    my ($vmid) = @_;
+
+    my $raw = read_cgroup_value('cpuacct', $vmid, 'cpuacct.stat', 1);
+
+    my $stat = {};
+
+    if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
+
+       $stat->{utime} = $1;
+       $stat->{stime} = $2;
+
+    }
+
+    return $stat;
+};
+
+sub vmstatus {
+    my ($opt_vmid) = @_;
+
+    my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
+
+    my $active_hash = list_active_containers();
+
+    my $cpucount = $cpuinfo->{cpus} || 1;
+
+    my $cdtime = gettimeofday;
+
+    my $uptime = (PVE::ProcFSTools::read_proc_uptime(1))[0];
+
+    foreach my $vmid (keys %$list) {
+       my $d = $list->{$vmid};
+
+       eval { $d->{pid} = find_lxc_pid($vmid) if defined($active_hash->{$vmid}); };
+       warn $@ if $@; # ignore errors (consider them stopped)
+
+       $d->{status} = $d->{pid} ? 'running' : 'stopped';
+
+       my $cfspath = cfs_config_path($vmid);
+       my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
+
+       $d->{name} = $conf->{'hostname'} || "CT$vmid";
+       $d->{name} =~ s/[\s]//g;
+
+       $d->{cpus} = $conf->{cpulimit} || $cpucount;
+
+       if ($d->{pid}) {
+           my $res = get_container_disk_usage($vmid, $d->{pid});
+           $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_rootfs($rootfs);
+               $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
+           } else {
+               $d->{maxdisk} = 4*1024*1024*1024;
            }
-           if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) {
-               my ($name, $value) = ($2, $3);
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{$name} = $value;
-               }
+       }
+
+       $d->{mem} = 0;
+       $d->{swap} = 0;
+       $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
+       $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
+
+       $d->{uptime} = 0;
+       $d->{cpu} = 0;
+
+       $d->{netout} = 0;
+       $d->{netin} = 0;
+
+       $d->{diskread} = 0;
+       $d->{diskwrite} = 0;
+
+       $d->{template} = is_template($conf);
+    }
+
+    foreach my $vmid (keys %$list) {
+       my $d = $list->{$vmid};
+       my $pid = $d->{pid};
+
+       next if !$pid; # skip stopped CTs
+
+       my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
+       $d->{uptime} = time - $ctime; # the method lxcfs uses
+
+       $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
+       $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
+
+       my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
+       my @bytes = split(/\n/, $blkio_bytes);
+       foreach my $byte (@bytes) {
+           if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
+               $d->{diskread} = $2 if $key eq 'Read';
+               $d->{diskwrite} = $2 if $key eq 'Write';
+           }
+       }
+
+       my $pstat = &$parse_cpuacct_stat($vmid);
+
+       my $used = $pstat->{utime} + $pstat->{stime};
+
+       my $old = $last_proc_vmid_stat->{$vmid};
+       if (!$old) {
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => 0,
+           };
+           next;
+       }
+
+       my $dtime = ($cdtime -  $old->{time}) * $cpucount * $cpuinfo->{user_hz};
+
+       if ($dtime > 1000) {
+           my $dutime = $used -  $old->{used};
+
+           $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus};
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => $d->{cpu},
+           };
+       } else {
+           $d->{cpu} = $old->{cpu};
+       }
+    }
+
+    my $netdev = PVE::ProcFSTools::read_proc_net_dev();
+
+    foreach my $dev (keys %$netdev) {
+       next if $dev !~ m/^veth([1-9]\d*)i/;
+       my $vmid = $1;
+       my $d = $list->{$vmid};
+
+       next if !$d;
+
+       $d->{netout} += $netdev->{$dev}->{receive};
+       $d->{netin} += $netdev->{$dev}->{transmit};
+
+    }
+
+    return $list;
+}
+
+sub classify_mountpoint {
+    my ($vol) = @_;
+    if ($vol =~ m!^/!) {
+       return 'device' if $vol =~ m!^/dev/!;
+       return 'bind';
+    }
+    return 'volume';
+}
+
+my $parse_ct_mountpoint_full = sub {
+    my ($desc, $data, $noerr) = @_;
+
+    $data //= '';
+
+    my $res;
+    eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
+    if ($@) {
+       return undef if $noerr;
+       die $@;
+    }
+
+    if (defined(my $size = $res->{size})) {
+       $size = PVE::JSONSchema::parse_size($size);
+       if (!defined($size)) {
+           return undef if $noerr;
+           die "invalid size: $size\n";
+       }
+       $res->{size} = $size;
+    }
+
+    $res->{type} = classify_mountpoint($res->{volume});
+
+    return $res;
+};
+
+sub parse_ct_rootfs {
+    my ($data, $noerr) = @_;
+
+    my $res =  &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
+
+    $res->{mp} = '/' if defined($res);
+
+    return $res;
+}
+
+sub parse_ct_mountpoint {
+    my ($data, $noerr) = @_;
+
+    return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
+}
+
+sub print_ct_mountpoint {
+    my ($info, $nomp) = @_;
+    my $skip = [ 'type' ];
+    push @$skip, 'mp' if $nomp;
+    return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
+}
+
+sub print_lxc_network {
+    my $net = shift;
+    return PVE::JSONSchema::print_property_string($net, $netconf_desc);
+}
+
+sub parse_lxc_network {
+    my ($data) = @_;
+
+    my $res = {};
+
+    return $res if !$data;
+
+    $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
+
+    $res->{type} = 'veth';
+    $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
+
+    return $res;
+}
+
+sub read_cgroup_value {
+    my ($group, $vmid, $name, $full) = @_;
+
+    my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
+
+    return PVE::Tools::file_get_contents($path) if $full;
+
+    return PVE::Tools::file_read_firstline($path);
+}
+
+sub write_cgroup_value {
+   my ($group, $vmid, $name, $value) = @_;
+
+   my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
+   PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
+
+}
+
+sub find_lxc_console_pids {
+
+    my $res = {};
+
+    PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
+       my ($pid) = @_;
+
+       my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
+       return if !$cmdline;
+
+       my @args = split(/\0/, $cmdline);
+
+       # search for lxc-console -n <vmid>
+       return if scalar(@args) != 3;
+       return if $args[1] ne '-n';
+       return if $args[2] !~ m/^\d+$/;
+       return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
+
+       my $vmid = $args[2];
+
+       push @{$res->{$vmid}}, $pid;
+    });
+
+    return $res;
+}
+
+sub find_lxc_pid {
+    my ($vmid) = @_;
+
+    my $pid = undef;
+    my $parser = sub {
+        my $line = shift;
+        $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
+    };
+    PVE::Tools::run_command(['lxc-info', '-n', $vmid, '-p'], outfunc => $parser);
+
+    die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
+
+    return $pid;
+}
+
+# Note: we cannot use Net:IP, because that only allows strict
+# CIDR networks
+sub parse_ipv4_cidr {
+    my ($cidr, $noerr) = @_;
+
+    if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) &&  ($2 <= 32)) {
+       return { address => $1, netmask => $PVE::Network::ipv4_reverse_mask->[$2] };
+    }
+
+    return undef if $noerr;
+
+    die "unable to parse ipv4 address/mask\n";
+}
+
+sub check_lock {
+    my ($conf) = @_;
+
+    die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
+}
+
+sub check_protection {
+    my ($vm_conf, $err_msg) = @_;
+
+    if ($vm_conf->{protection}) {
+       die "$err_msg - protection mode enabled\n";
+    }
+}
+
+sub update_lxc_config {
+    my ($storage_cfg, $vmid, $conf) = @_;
+
+    my $dir = "/var/lib/lxc/$vmid";
+
+    if ($conf->{template}) {
+
+       unlink "$dir/config";
+
+       return;
+    }
+
+    my $raw = '';
+
+    die "missing 'arch' - internal error" if !$conf->{arch};
+    $raw .= "lxc.arch = $conf->{arch}\n";
+
+    my $unprivileged = $conf->{unprivileged};
+    my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
+
+    my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
+    if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
+       $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
+       if ($unprivileged || $custom_idmap) {
+           $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
+       }
+    } else {
+       die "implement me (ostype $ostype)";
+    }
+
+    $raw .= "lxc.monitor.unshare = 1\n";
+
+    # Should we read them from /etc/subuid?
+    if ($unprivileged && !$custom_idmap) {
+       $raw .= "lxc.id_map = u 0 100000 65536\n";
+       $raw .= "lxc.id_map = g 0 100000 65536\n";
+    }
+
+    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";
+
+    # some init scripts expect a linux terminal (turnkey).
+    $raw .= "lxc.environment = TERM=linux\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";
+    }
+
+    my $shares = $conf->{cpuunits} || 1024;
+    $raw .= "lxc.cgroup.cpu.shares = $shares\n";
+
+    my $mountpoint = parse_ct_rootfs($conf->{rootfs});
+
+    $raw .= "lxc.rootfs = $dir/rootfs\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 $lxcconf = $conf->{lxc}) {
+       foreach my $entry (@$lxcconf) {
+           my ($k, $v) = @$entry;
+           $netcount++ if $k eq 'lxc.network.type';
+           $raw .= "$k = $v\n";
+       }
+    }
+
+    $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 ' ')
+sub verify_nameserver_list {
+    my ($nameserver_list) = @_;
+
+    my @list = ();
+    foreach my $server (PVE::Tools::split_list($nameserver_list)) {
+       PVE::JSONSchema::pve_verify_ip($server);
+       push @list, $server;
+    }
+
+    return join(' ', @list);
+}
+
+sub verify_searchdomain_list {
+    my ($searchdomain_list) = @_;
+
+    my @list = ();
+    foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
+       # todo: should we add checks for valid dns domains?
+       push @list, $server;
+    }
+
+    return join(' ', @list);
+}
+
+sub add_unused_volume {
+    my ($config, $volid) = @_;
+
+    my $key;
+    for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
+       my $test = "unused$ind";
+       if (my $vid = $config->{$test}) {
+           return if $vid eq $volid; # do not add duplicates
+       } else {
+           $key = $test;
+       }
+    }
+
+    die "Too many unused volumes - please delete them first.\n" if !$key;
+
+    $config->{$key} = $volid;
+
+    return $key;
+}
+
+sub update_pct_config {
+    my ($vmid, $conf, $running, $param, $delete) = @_;
+
+    my @nohotplug;
+
+    my $new_disks = 0;
+    my @deleted_volumes;
+
+    my $rootdir;
+    if ($running) {
+       my $pid = find_lxc_pid($vmid);
+       $rootdir = "/proc/$pid/root";
+    }
+
+    my $hotplug_error = sub {
+       if ($running) {
+           push @nohotplug, @_;
+           return 1;
+       } else {
+           return 0;
+       }
+    };
+
+    if (defined($delete)) {
+       foreach my $opt (@$delete) {
+           if (!exists($conf->{$opt})) {
+               warn "no such option: $opt\n";
                next;
            }
-           if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) {
-               if (!$snapname) {
-                   $snapname = $2;
-                   $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
-               } else {
-                   die "Configuarion broken\n";
+
+           if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
+               die "unable to delete required option '$opt'\n";
+           } elsif ($opt eq 'swap') {
+               delete $conf->{$opt};
+               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
+           } 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') {
+               next if $hotplug_error->($opt);
+               delete $conf->{$opt};
+           } elsif ($opt =~ m/^net(\d)$/) {
+               delete $conf->{$opt};
+               next if !$running;
+               my $netid = $1;
+               PVE::Network::veth_delete("veth${vmid}i$netid");
+           } elsif ($opt eq 'protection') {
+               delete $conf->{$opt};
+           } elsif ($opt =~ m/^unused(\d+)$/) {
+               next if $hotplug_error->($opt);
+               check_protection($conf, "can't remove CT $vmid drive '$opt'");
+               push @deleted_volumes, $conf->{$opt};
+               delete $conf->{$opt};
+           } elsif ($opt =~ m/^mp(\d+)$/) {
+               next if $hotplug_error->($opt);
+               check_protection($conf, "can't remove CT $vmid drive '$opt'");
+               my $mountpoint = parse_ct_mountpoint($conf->{$opt});
+               if ($mountpoint->{type} eq 'volume') {
+                   add_unused_volume($conf, $mountpoint->{volume})
                }
-               next;
+               delete $conf->{$opt};
+           } elsif ($opt eq 'unprivileged') {
+               die "unable to delete read-only option: '$opt'\n";
+           } else {
+               die "implement me (delete: $opt)"
            }
-           if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
-               my ($name, $value) = ($2, $3);
+           write_config($vmid, $conf) if $running;
+       }
+    }
 
-               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);
-                   }
-               }
+    # 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)) {
 
-               next;
+       my $old_memory = ($conf->{memory} || 512);
+       my $old_swap = ($conf->{swap} || 0);
+
+       $wanted_memory //= $old_memory;
+       $wanted_swap //= $old_swap;
+
+        my $total = $wanted_memory + $wanted_swap;
+       if ($running) {
+           my $old_total = $old_memory + $old_swap;
+           if ($total > $old_total) {
+               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
+               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
+           } else {
+               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));
            }
-           die "unable to parse config line: $line\n";
        }
-       &$push_network($network);
+       $conf->{memory} = $wanted_memory;
+       $conf->{swap} = $wanted_swap;
 
-       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';
+       write_config($vmid, $conf) if $running;
+    }
 
-           if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{"net$1"} = $net;
-               } else {
-                   $data->{"net$1"} = $net;
-               }
+    foreach my $opt (keys %$param) {
+       my $value = $param->{$opt};
+       if ($opt eq 'hostname') {
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'onboot') {
+           $conf->{$opt} = $value ? 1 : 0;
+       } elsif ($opt eq 'startup') {
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+           next if $hotplug_error->($opt);
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'nameserver') {
+           next if $hotplug_error->($opt);
+           my $list = verify_nameserver_list($value);
+           $conf->{$opt} = $list;
+       } elsif ($opt eq 'searchdomain') {
+           next if $hotplug_error->($opt);
+           my $list = verify_searchdomain_list($value);
+           $conf->{$opt} = $list;
+       } elsif ($opt eq 'cpulimit') {
+           next if $hotplug_error->($opt); # FIXME: hotplug
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'cpuunits') {
+           $conf->{$opt} = $value;
+           write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
+       } elsif ($opt eq 'description') {
+           $conf->{$opt} = PVE::Tools::encode_text($value);
+       } elsif ($opt =~ m/^net(\d+)$/) {
+           my $netid = $1;
+           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+)$/) {
+           next if $hotplug_error->($opt);
+           check_protection($conf, "can't update CT $vmid drive '$opt'");
+           $conf->{$opt} = $value;
+           $new_disks = 1;
+        } elsif ($opt eq 'rootfs') {
+           check_protection($conf, "can't update CT $vmid drive '$opt'");
+           die "implement me: $opt";
+       } elsif ($opt eq 'unprivileged') {
+           die "unable to modify read-only option: '$opt'\n";
+       } else {
+           die "implement me: $opt";
+       }
+       write_config($vmid, $conf) if $running;
+    }
+
+    if (@deleted_volumes) {
+       my $storage_cfg = PVE::Storage::config();
+       foreach my $volume (@deleted_volumes) {
+           delete_mountpoint_volume($storage_cfg, $vmid, $volume);
        }
     }
 
-    return $data;
+    if ($new_disks) {
+       my $storage_cfg = PVE::Storage::config();
+       create_disks($storage_cfg, $vmid, $conf, $conf);
+    }
+
+    # This should be the last thing we do here
+    if ($running && scalar(@nohotplug)) {
+       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
+    }
 }
 
-sub config_list {
-    my $vmlist = PVE::Cluster::get_vmlist();
-    my $res = {};
-    return $res if !$vmlist || !$vmlist->{ids};
-    my $ids = $vmlist->{ids};
+sub has_dev_console {
+    my ($conf) = @_;
 
-    foreach my $vmid (keys %$ids) {
-       next if !$vmid; # skip CT0
-       my $d = $ids->{$vmid};
-       next if !$d->{node} || $d->{node} ne $nodename;
-       next if !$d->{type} || $d->{type} ne 'lxc';
-       $res->{$vmid}->{type} = 'lxc';
+    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";
     }
-    return $res;
 }
 
-sub cfs_config_path {
-    my ($vmid, $node) = @_;
+sub get_primary_ips {
+    my ($conf) = @_;
 
-    $node = $nodename if !$node;
-    return "nodes/$node/lxc/$vmid/config";
+    # return data from net0
+
+    return undef if !defined($conf->{net0});
+    my $net = parse_lxc_network($conf->{net0});
+
+    my $ipv4 = $net->{ip};
+    if ($ipv4) {
+       if ($ipv4 =~ /^(dhcp|manual)$/) {
+           $ipv4 = undef
+       } else {
+           $ipv4 =~ s!/\d+$!!;
+       }
+    }
+    my $ipv6 = $net->{ip6};
+    if ($ipv6) {
+       if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
+           $ipv6 = undef;
+       } else {
+           $ipv6 =~ s!/\d+$!!;
+       }
+    }
+
+    return ($ipv4, $ipv6);
 }
 
-sub config_file {
-    my ($vmid, $node) = @_;
+sub delete_mountpoint_volume {
+    my ($storage_cfg, $vmid, $volume) = @_;
 
-    my $cfspath = cfs_config_path($vmid, $node);
-    return "/etc/pve/$cfspath";
+    return if classify_mountpoint($volume) ne 'volume';
+
+    my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
+    PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
 }
 
-sub load_config {
-    my ($vmid) = @_;
+sub destroy_lxc_container {
+    my ($storage_cfg, $vmid, $conf) = @_;
 
-    my $cfspath = cfs_config_path($vmid);
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+       delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
+    });
 
-    my $conf = PVE::Cluster::cfs_read_file($cfspath);
-    die "container $vmid does not exists\n" if !defined($conf);
+    rmdir "/var/lib/lxc/$vmid/rootfs";
+    unlink "/var/lib/lxc/$vmid/config";
+    rmdir "/var/lib/lxc/$vmid";
+    destroy_config($vmid);
 
-    return $conf;
+    #my $cmd = ['lxc-destroy', '-n', $vmid ];
+    #PVE::Tools::run_command($cmd);
 }
 
-sub create_config {
-    my ($vmid, $conf) = @_;
+sub vm_stop_cleanup {
+    my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
+    
+    eval {
+       if (!$keepActive) {
 
-    my $dir = "/etc/pve/nodes/$nodename/lxc";
-    mkdir $dir;
+            my $vollist = get_vm_volumes($conf);
+           PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
+       }
+    };
+    warn $@ if $@; # avoid errors - just warn
+}
+
+my $safe_num_ne = sub {
+    my ($a, $b) = @_;
+
+    return 0 if !defined($a) && !defined($b);
+    return 1 if !defined($a);
+    return 1 if !defined($b);
+
+    return $a != $b;
+};
+
+my $safe_string_ne = sub {
+    my ($a, $b) = @_;
+
+    return 0 if !defined($a) && !defined($b);
+    return 1 if !defined($a);
+    return 1 if !defined($b);
+
+    return $a ne $b;
+};
+
+sub update_net {
+    my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
+
+    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 (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);
+           delete $conf->{$opt};
+           write_config($vmid, $conf);
+
+           hotplug_net($vmid, $conf, $opt, $newnet, $netid);
+
+       } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+                &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
+                &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
+
+               if ($oldnet->{bridge}) {
+                   PVE::Network::tap_unplug($veth);
+                   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}, $newnet->{trunks});
+               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, $netid);
+    }
+
+    update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
+}
+
+sub hotplug_net {
+    my ($vmid, $conf, $opt, $newnet, $netid) = @_;
+
+    my $veth = "veth${vmid}i${netid}";
+    my $vethpeer = $veth . "p";
+    my $eth = $newnet->{name};
+
+    PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
+    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
+
+    # attach peer in container
+    my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
+    PVE::Tools::run_command($cmd);
+
+    # link up peer in container
+    $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up'  ];
+    PVE::Tools::run_command($cmd);
+
+    my $done = { type => 'veth' };
+    foreach (qw(bridge tag firewall hwaddr name)) {
+       $done->{$_} = $newnet->{$_} if $newnet->{$_};
+    }
+    $conf->{$opt} = print_lxc_network($done);
+
+    write_config($vmid, $conf);
+}
+
+sub update_ipconfig {
+    my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
+
+    my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
+
+    my $optdata = parse_lxc_network($conf->{$opt});
+    my $deleted = [];
+    my $added = [];
+    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) = @_;
+
+       my $family_opt = "-$ipversion";
+       my $suffix = $ipversion == 4 ? '' : $ipversion;
+       my $gw= "gw$suffix";
+       my $ip= "ip$suffix";
+
+       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
+       my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
+       if ($change_ip && $is_real_ip) {
+           eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
+           if (my $err = $@) {
+               warn $err;
+               return;
+           }
+       }
+
+       # step 2: replace gateway
+       #   If this fails we delete the added IP and cancel.
+       #   If it succeeds we save the config and delete the old IP, ignoring
+       #   errors. The config is then saved.
+       # Note: 'ip route replace' can add
+       if ($change_gw) {
+           if ($newgw) {
+               eval {
+                   if ($is_real_ip && !PVE::Network::is_ip_in_cidr($newgw, $newip, $ipversion)) {
+                       &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
+                   }
+                   &$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 { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
+                       warn $@ if $@; # no need to die here
+                   }
+                   return;
+               }
+           } else {
+               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 save the configuration
+       # step 3: delete old IP ignoring errors
+       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) {
+           if ($newnet->{$property}) {
+               $optdata->{$property} = $newnet->{$property};
+           } else {
+               delete $optdata->{$property};
+           }
+       }
+       $conf->{$opt} = print_lxc_network($optdata);
+       write_config($vmid, $conf);
+       $lxc_setup->setup_network($conf);
+    };
 
-    $dir .= "/$vmid";
-    mkdir($dir) || die "unable to create container configuration directory - $!\n";
+    &$change_ip_config(4);
+    &$change_ip_config(6);
 
-    write_config($vmid, $conf);
 }
 
-sub destroy_config {
-    my ($vmid) = @_;
+# Internal snapshots
 
-    my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
-    File::Path::rmtree($dir);
-}
+# NOTE: Snapshot create/delete involves several non-atomic
+# actions, and can take a long time.
+# So we try to avoid locking the file and use the 'lock' variable
+# inside the config file instead.
 
-sub write_config {
-    my ($vmid, $conf) = @_;
+my $snapshot_copy_config = sub {
+    my ($source, $dest) = @_;
 
-    my $cfspath = cfs_config_path($vmid);
+    foreach my $k (keys %$source) {
+       next if $k eq 'snapshots';
+       next if $k eq 'snapstate';
+       next if $k eq 'snaptime';
+       next if $k eq 'vmstate';
+       next if $k eq 'lock';
+       next if $k eq 'digest';
+       next if $k eq 'description';
 
-    PVE::Cluster::cfs_write_file($cfspath, $conf);
-}
+       $dest->{$k} = $source->{$k};
+    }
+};
 
-my $tempcounter = 0;
-sub write_temp_config {
-    my ($vmid, $conf) = @_;
+my $snapshot_prepare = sub {
+    my ($vmid, $snapname, $comment) = @_;
 
-    $tempcounter++;
-    my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
+    my $snap;
 
-    my $raw =  write_lxc_config($filename, $conf);
+    my $updatefn =  sub {
 
-    PVE::Tools::file_set_contents($filename, $raw);
+       my $conf = load_config($vmid);
 
-    return $filename;
-}
+       die "you can't take a snapshot if it's a template\n"
+           if is_template($conf);
 
-# flock: we use one file handle per process, so lock file
-# can be called multiple times and succeeds for the same process.
+       check_lock($conf);
 
-my $lock_handles =  {};
-my $lockdir = "/run/lock/lxc";
+       $conf->{lock} = 'snapshot';
 
-sub lock_filename {
-    my ($vmid) = @_;
+       die "snapshot name '$snapname' already used\n"
+           if defined($conf->{snapshots}->{$snapname});
 
-    return "$lockdir/pve-config-{$vmid}.lock";
-}
+       my $storecfg = PVE::Storage::config();
+       my $feature = $snapname eq 'vzdump' ? 'vzdump' : 'snapshot';
+       die "snapshot feature is not available\n" if !has_feature($feature, $conf, $storecfg);
 
-sub lock_aquire {
-    my ($vmid, $timeout) = @_;
+       $snap = $conf->{snapshots}->{$snapname} = {};
 
-    $timeout = 10 if !$timeout;
-    my $mode = LOCK_EX;
+       &$snapshot_copy_config($conf, $snap);
 
-    my $filename = lock_filename($vmid);
+       $snap->{'snapstate'} = "prepare";
+       $snap->{'snaptime'} = time();
+       $snap->{'description'} = $comment if $comment;
+       $conf->{snapshots}->{$snapname} = $snap;
 
-    mkdir $lockdir if !-d $lockdir;
+       write_config($vmid, $conf);
+    };
 
-    my $lock_func = sub {
-       if (!$lock_handles->{$$}->{$filename}) {
-           my $fh = new IO::File(">>$filename") ||
-               die "can't open file - $!\n";
-           $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
-       }
+    lock_container($vmid, 10, $updatefn);
 
-       if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
-           print STDERR "trying to aquire lock...";
-           my $success;
-           while(1) {
-               $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
-               # try again on EINTR (see bug #273)
-               if ($success || ($! != EINTR)) {
-                   last;
-               }
-           }
-           if (!$success) {
-               print STDERR " failed\n";
-               die "can't aquire lock - $!\n";
-           }
+    return $snap;
+};
 
-           $lock_handles->{$$}->{$filename}->{refcount}++;
+my $snapshot_commit = sub {
+    my ($vmid, $snapname) = @_;
 
-           print STDERR " OK\n";
-       }
-    };
+    my $updatefn = sub {
 
-    eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
-    my $err = $@;
-    if ($err) {
-       die "can't lock file '$filename' - $err";
-    }
-}
+       my $conf = load_config($vmid);
 
-sub lock_release {
-    my ($vmid) = @_;
+       die "missing snapshot lock\n"
+           if !($conf->{lock} && $conf->{lock} eq 'snapshot');
 
-    my $filename = lock_filename($vmid);
+       die "snapshot '$snapname' does not exist\n"
+           if !defined($conf->{snapshots}->{$snapname});
 
-    if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
-       my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
-       if ($refcount <= 0) {
-           $lock_handles->{$$}->{$filename} = undef;
-           close ($fh);
-       }
-    }
-}
+       die "wrong snapshot state\n"
+           if !($conf->{snapshots}->{$snapname}->{'snapstate'} && 
+                $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
 
-sub lock_container {
-    my ($vmid, $timeout, $code, @param) = @_;
+       delete $conf->{snapshots}->{$snapname}->{'snapstate'};
+       delete $conf->{lock};
+       $conf->{parent} = $snapname;
 
-    my $res;
+       write_config($vmid, $conf);
+    };
 
-    lock_aquire($vmid, $timeout);
-    eval { $res = &$code(@param) };
-    my $err = $@;
-    lock_release($vmid);
+    lock_container($vmid, 10 ,$updatefn);
+};
 
-    die $err if $err;
+sub has_feature {
+    my ($feature, $conf, $storecfg, $snapname) = @_;
+    
+    my $err;
+    my $vzdump = $feature eq 'vzdump';
+    $feature = 'snapshot' if $vzdump;
 
-    return $res;
-}
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
 
-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.",
-    },
-};
+       return if $err; # skip further test
+       return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup};
+       
+       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
 
-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>]",
-    };
+       # TODO: implement support for mountpoints
+       die "unable to handle mountpoint '$ms' - feature not implemented\n"
+           if $ms ne 'rootfs';
+    });
+
+    return $err ? 0 : 1;
 }
 
-sub option_exists {
-    my ($name) = @_;
+sub snapshot_create {
+    my ($vmid, $snapname, $comment) = @_;
 
-    return defined($confdesc->{$name});
-}
+    my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
 
-# add JSON properties for create and set function
-sub json_config_properties {
-    my $prop = shift;
+    my $conf = load_config($vmid);
 
-    foreach my $opt (keys %$confdesc) {
-       $prop->{$opt} = $confdesc->{$opt};
-    }
+    my $running = check_running($vmid);
+    
+    my $unfreeze = 0;
+    
+    eval {
+       if ($running) {
+           PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
+           $unfreeze = 1;
+           PVE::Tools::run_command(['/bin/sync']);
+       };
 
-    return $prop;
+       my $storecfg = PVE::Storage::config();
+       my $rootinfo = parse_ct_rootfs($conf->{rootfs});
+       my $volid = $rootinfo->{volume};
+
+       PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
+       &$snapshot_commit($vmid, $snapname);
+    };
+    my $err = $@;
+    
+    if ($unfreeze) {
+       eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
+       warn $@ if $@;
+    }
+    
+    if ($err) {
+       snapshot_delete($vmid, $snapname, 1);
+       die "$err\n";
+    }
 }
 
-# container status helpers
+sub snapshot_delete {
+    my ($vmid, $snapname, $force) = @_;
 
-sub list_active_containers {
+    my $snap;
 
-    my $filename = "/proc/net/unix";
+    my $conf;
 
-    # similar test is used by lcxcontainers.c: list_active_containers
-    my $res = {};
+    my $updatefn =  sub {
 
-    my $fh = IO::File->new ($filename, "r");
-    return $res if !$fh;
+       $conf = load_config($vmid);
 
-    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$!) {
-               $res->{$1} = 1;
-           }
-       }
-    }
+       die "you can't delete a snapshot if vm is a template\n"
+           if is_template($conf);
 
-    close($fh);
+       $snap = $conf->{snapshots}->{$snapname};
 
-    return $res;
-}
+       check_lock($conf);
 
-# warning: this is slow
-sub check_running {
-    my ($vmid) = @_;
+       die "snapshot '$snapname' does not exist\n" if !defined($snap);
 
-    my $active_hash = list_active_containers();
+       $snap->{snapstate} = 'delete';
 
-    return 1 if defined($active_hash->{$vmid});
+       write_config($vmid, $conf);
+    };
 
-    return undef;
-}
+    lock_container($vmid, 10, $updatefn);
 
-sub get_container_disk_usage {
-    my ($vmid) = @_;
+    my $storecfg = PVE::Storage::config();
 
-    my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df',  '-P', '-B', '1', '/'];
+    my $unlink_parent = sub {
 
-    my $res = {
-       total => 0,
-       used => 0,
-       avail => 0,
-    };
+       my ($confref, $new_parent) = @_;
 
-    my $parser = sub {
-       my $line = shift;
-       if (my ($fsid, $total, $used, $avail) = $line =~
-           m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
-           $res = {
-               total => $total,
-               used => $used,
-               avail => $avail,
-           };
+       if ($confref->{parent} && $confref->{parent} eq $snapname) {
+           if ($new_parent) {
+               $confref->{parent} = $new_parent;
+           } else {
+               delete $confref->{parent};
+           }
        }
     };
-    eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
-    warn $@ if $@;
-
-    return $res;
-}
-
-sub vmstatus {
-    my ($opt_vmid) = @_;
-
-    my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
 
-    my $active_hash = list_active_containers();
+    my $del_snap =  sub {
 
-    foreach my $vmid (keys %$list) {
-       my $d = $list->{$vmid};
+       check_lock($conf);
 
-       my $running = defined($active_hash->{$vmid});
+       my $parent = $conf->{snapshots}->{$snapname}->{parent};
+       foreach my $snapkey (keys %{$conf->{snapshots}}) {
+           &$unlink_parent($conf->{snapshots}->{$snapkey}, $parent);
+       }
 
-       $d->{status} = $running ? 'running' : 'stopped';
+       &$unlink_parent($conf, $parent);
 
-       my $cfspath = cfs_config_path($vmid);
-       my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
+       delete $conf->{snapshots}->{$snapname};
 
-       $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
-       $d->{name} =~ s/[\s]//g;
+       write_config($vmid, $conf);
+    };
 
-       $d->{cpus} = 0;
+    my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
+    my $rootinfo = parse_ct_rootfs($rootfs);
+    my $volid = $rootinfo->{volume};
 
-       my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
-       my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
+    eval {
+       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
+    };
+    my $err = $@;
 
-       if ($cfs_period_us && $cfs_quota_us) {
-           $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
+    if(!$err || ($err && $force)) {
+       lock_container($vmid, 10, $del_snap);
+       if ($err) {
+           die "Can't delete snapshot: $vmid $snapname $err\n";
        }
+    }
+}
 
-       $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};
-               }
-           }
-       }
+sub snapshot_rollback {
+    my ($vmid, $snapname) = @_;
 
-       $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);
+    my $storecfg = PVE::Storage::config();
 
-       $d->{uptime} = 0;
-       $d->{cpu} = 0;
+    my $conf = load_config($vmid);
 
-       $d->{netout} = 0;
-       $d->{netin} = 0;
+    die "you can't rollback if vm is a template\n" if is_template($conf);
 
-       $d->{diskread} = 0;
-       $d->{diskwrite} = 0;
-    }
+    my $snap = $conf->{snapshots}->{$snapname};
 
-    foreach my $vmid (keys %$list) {
-       my $d = $list->{$vmid};
-       next if $d->{status} ne 'running';
+    die "snapshot '$snapname' does not exist\n" if !defined($snap);
 
-       $d->{uptime} = 100; # fixme:
+    my $rootfs = $snap->{rootfs};
+    my $rootinfo = parse_ct_rootfs($rootfs);
+    my $volid = $rootinfo->{volume};
 
-       $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
-       $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
+    PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
 
-       my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
-       my @bytes = split(/\n/, $blkio_bytes);
-       foreach my $byte (@bytes) {
-           if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
-               $d->{diskread} = $2 if $key eq 'Read';
-               $d->{diskwrite} = $2 if $key eq 'Write';
-           }
-       }
-    }
+    my $updatefn = sub {
 
-    return $list;
-}
+       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" 
+           if $snap->{snapstate};
 
+       check_lock($conf);
 
-sub print_lxc_network {
-    my $net = shift;
+       system("lxc-stop -n $vmid --kill") if check_running($vmid);
 
-    die "no network name defined\n" if !$net->{name};
+       die "unable to rollback vm $vmid: vm is running\n"
+           if check_running($vmid);
 
-    my $res = "name=$net->{name}";
+       $conf->{lock} = 'rollback';
 
-    foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
-       next if !defined($net->{$k});
-       $res .= ",$k=$net->{$k}";
-    }
+       my $forcemachine;
 
-    return $res;
-}
+       # copy snapshot config to current config
 
-sub parse_lxc_network {
-    my ($data) = @_;
+       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;
 
-    my $res = {};
+       write_config($vmid, $conf);
+    };
 
-    return $res if !$data;
+    my $unlockfn = sub {
+       delete $conf->{lock};
+       write_config($vmid, $conf);
+    };
 
-    foreach my $pv (split (/,/, $data)) {
-       if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
-           $res->{$1} = $2;
-       } else {
-           return undef;
-       }
-    }
+    lock_container($vmid, 10, $updatefn);
 
-    $res->{type} = 'veth';
-    $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
+    PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
 
-    return $res;
+    lock_container($vmid, 5, $unlockfn);
 }
 
-sub read_cgroup_value {
-    my ($group, $vmid, $name, $full) = @_;
+sub template_create {
+    my ($vmid, $conf) = @_;
 
-    my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
+    my $storecfg = PVE::Storage::config();
 
-    return PVE::Tools::file_get_contents($path) if $full;
+    my $rootinfo = parse_ct_rootfs($conf->{rootfs});
+    my $volid = $rootinfo->{volume};
 
-    return PVE::Tools::file_read_firstline($path);
-}
+    die "Template feature is not available for '$volid'\n"
+       if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
 
-sub write_cgroup_value {
-   my ($group, $vmid, $name, $value) = @_;
+    PVE::Storage::activate_volumes($storecfg, [$volid]);
 
-   my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
-   PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
+    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 find_lxc_console_pids {
+sub is_template {
+    my ($conf) = @_;
 
-    my $res = {};
+    return 1 if defined $conf->{template} && $conf->{template} == 1;
+}
 
-    PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
-       my ($pid) = @_;
+sub mountpoint_names {
+    my ($reverse) = @_;
 
-       my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
-       return if !$cmdline;
+    my @names = ('rootfs');
 
-       my @args = split(/\0/, $cmdline);
+    for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+       push @names, "mp$i";
+    }
+
+    return $reverse ? reverse @names : @names;
+}
 
-       # serach for lxc-console -n <vmid>
-       return if scalar(@args) != 3;
-       return if $args[1] ne '-n';
-       return if $args[2] !~ m/^\d+$/;
-       return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
 
-       my $vmid = $args[2];
+sub foreach_mountpoint_full {
+    my ($conf, $reverse, $func) = @_;
 
-       push @{$res->{$vmid}}, $pid;
-    });
+    foreach my $key (mountpoint_names($reverse)) {
+       my $value = $conf->{$key};
+       next if !defined($value);
+       my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
+       next if !defined($mountpoint);
 
-    return $res;
+       &$func($key, $mountpoint);
+    }
 }
 
-sub find_lxc_pid {
-    my ($vmid) = @_;
+sub foreach_mountpoint {
+    my ($conf, $func) = @_;
 
-    my $pid = undef;
-    my $parser = sub {
-        my $line = shift;
-        $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
-    };
-    PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
+    foreach_mountpoint_full($conf, 0, $func);
+}
 
-    die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
+sub foreach_mountpoint_reverse {
+    my ($conf, $func) = @_;
 
-    return $pid;
+    foreach_mountpoint_full($conf, 1, $func);
 }
 
-my $ipv4_reverse_mask = [
-    '0.0.0.0',
-    '128.0.0.0',
-    '192.0.0.0',
-    '224.0.0.0',
-    '240.0.0.0',
-    '248.0.0.0',
-    '252.0.0.0',
-    '254.0.0.0',
-    '255.0.0.0',
-    '255.128.0.0',
-    '255.192.0.0',
-    '255.224.0.0',
-    '255.240.0.0',
-    '255.248.0.0',
-    '255.252.0.0',
-    '255.254.0.0',
-    '255.255.0.0',
-    '255.255.128.0',
-    '255.255.192.0',
-    '255.255.224.0',
-    '255.255.240.0',
-    '255.255.248.0',
-    '255.255.252.0',
-    '255.255.254.0',
-    '255.255.255.0',
-    '255.255.255.128',
-    '255.255.255.192',
-    '255.255.255.224',
-    '255.255.255.240',
-    '255.255.255.248',
-    '255.255.255.252',
-    '255.255.255.254',
-    '255.255.255.255',
-];
+sub check_ct_modify_config_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
 
-# Note: we cannot use Net:IP, because that only allows strict
-# CIDR networks
-sub parse_ipv4_cidr {
-    my ($cidr, $noerr) = @_;
+    return 1 if $authuser ne 'root@pam';
 
-    if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) &&  ($2 < 32)) {
-       return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
-    }
+    foreach my $opt (@$key_list) {
 
-    return undef if $noerr;
+       if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
+       } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
+           $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']);
+       }
+    }
 
-    die "unable to parse ipv4 address/mask\n";
+    return 1;
 }
 
-sub check_lock {
-    my ($conf) = @_;
+sub umount_all {
+    my ($vmid, $storage_cfg, $conf, $noerr) = @_;
 
-    die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
-}
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    my $volid_list = get_vm_volumes($conf);
 
-sub lxc_conf_to_pve {
-    my ($vmid, $lxc_conf) = @_;
+    foreach_mountpoint_reverse($conf, sub {
+       my ($ms, $mountpoint) = @_;
 
-    my $properties = json_config_properties();
+       my $volid = $mountpoint->{volume};
+       my $mount = $mountpoint->{mp};
 
-    my $conf = { digest => $lxc_conf->{digest} };
+       return if !$volid || !$mount;
 
-    foreach my $k (keys %$properties) {
+       my $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
 
-       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 !PVE::ProcFSTools::is_mounted($mount_path);
 
-           if ($cfs_period_us && $cfs_quota_us) {
-               $conf->{$k} = $cfs_quota_us/$cfs_period_us;
+       eval {
+           PVE::Tools::run_command(['umount', '-d', $mount_path]);
+       };
+       if (my $err = $@) {
+           if ($noerr) {
+               warn $err;
            } else {
-               $conf->{$k} = 0;
+               die $err;
            }
-       } 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);
        }
-    }
+    });
+}
 
-    if (my $parent = $lxc_conf->{'pve.parent'}) {
-           $conf->{parent} = $lxc_conf->{'pve.parent'};
-    }
+sub mount_all {
+    my ($vmid, $storage_cfg, $conf) = @_;
 
-    if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
-       $conf->{description} = $lxc_conf->{'pve.snapcomment'};
-    }
+    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) = @_;
 
-    if (my $parent = $lxc_conf->{'pve.snaptime'}) {
-       $conf->{snaptime} = $lxc_conf->{'pve.snaptime'};
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+        });
+    };
+    if (my $err = $@) {
+       warn "mounting container failed\n";
+       umount_all($vmid, $storage_cfg, $conf, 1);
+       die $err;
     }
 
-    return $conf;
+    return $rootdir;
 }
 
-# verify and cleanup nameserver list (replace \0 with ' ')
-sub verify_nameserver_list {
-    my ($nameserver_list) = @_;
 
-    my @list = ();
-    foreach my $server (PVE::Tools::split_list($nameserver_list)) {
-       PVE::JSONSchema::pve_verify_ip($server);
-       push @list, $server;
-    }
+sub mountpoint_mount_path {
+    my ($mountpoint, $storage_cfg, $snapname) = @_;
 
-    return join(' ', @list);
+    return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
 }
 
-sub verify_searchdomain_list {
-    my ($searchdomain_list) = @_;
-
-    my @list = ();
-    foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
-       # todo: should we add checks for valid dns domains?
-       push @list, $server;
+my $check_mount_path = sub {
+    my ($path) = @_;
+    $path = File::Spec->canonpath($path);
+    my $real = Cwd::realpath($path);
+    if ($real ne $path) {
+       die "mount path modified by symlink: $path != $real";
     }
+};
 
-    return join(' ', @list);
+sub query_loopdev {
+    my ($path) = @_;
+    my $found;
+    my $parser = sub {
+       my $line = shift;
+       if ($line =~ m@^(/dev/loop\d+):@) {
+           $found = $1;
+       }
+    };
+    my $cmd = ['losetup', '--associated', $path];
+    PVE::Tools::run_command($cmd, outfunc => $parser);
+    return $found;
 }
 
-sub update_lxc_config {
-    my ($vmid, $conf, $running, $param, $delete) = @_;
+# 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};
+    my $type = $mountpoint->{type};
+    
+    return if !$volid || !$mount;
+
+    my $mount_path;
+    
+    if (defined($rootdir)) {
+       $rootdir =~ s!/+$!!;
+       $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
+       &$check_mount_path($mount_path);
+       File::Path::mkpath($mount_path);
+    }
+    
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-    my @nohotplug;
+    die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
 
-    my $rootdir;
-    if ($running) {
-       my $pid = find_lxc_pid($vmid);
-       $rootdir = "/proc/$pid/root";
+    my $optstring = '';
+    if (defined($mountpoint->{acl})) {
+       $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl');
     }
-
-    if (defined($delete)) {
-       foreach my $opt (@$delete) {
-           if ($opt eq 'hostname' || $opt eq 'memory') {
-               die "unable to delete required option '$opt'\n";
-           } elsif ($opt eq 'swap') {
-               delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
-               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'};
-               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");
-           } else {
-               die "implement me"
-           }
-           PVE::LXC::write_config($vmid, $conf) if $running;
-       }
+    if ($mountpoint->{ro}) {
+       $optstring .= ',' if $optstring;
+       $optstring .= 'ro';
     }
 
-    foreach my $opt (keys %$param) {
-       my $value = $param->{$opt};
-       if ($opt eq 'hostname') {
-           $conf->{'lxc.utsname'} = $value;
-       } elsif ($opt eq 'onboot') {
-           $conf->{'pve.onboot'} = $value ? 1 : 0;
-       } elsif ($opt eq 'startup') {
-           $conf->{'pve.startup'} = $value;
-       } elsif ($opt eq 'nameserver') {
-           my $list = verify_nameserver_list($value);
-           $conf->{'pve.nameserver'} = $list;
-           push @nohotplug, $opt;
-           next if $running;
-       } elsif ($opt eq 'searchdomain') {
-           my $list = verify_searchdomain_list($value);
-           $conf->{'pve.searchdomain'} = $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);
+    my @extra_opts = ('-o', $optstring);
 
-       } 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);
+    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);
+
+       $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
+
+       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', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
+                   } else {
+                       die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
+                   }
+               } else {
+                   if ($mountpoint->{ro}) {
+                       die "read-only bind mounts not supported\n";
+                   }
+                   PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
+               }
            }
-       } elsif ($opt eq 'cpuunits') {
-           $conf->{'lxc.cgroup.cpu.shares'} = $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;
-       } 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;
+           return wantarray ? ($path, 0) : $path;
+       } elsif ($format eq 'raw' || $format eq 'iso') {
+           my $use_loopdev = 0;
+           if ($scfg->{path}) {
+               push @extra_opts, '-o', 'loop';
+               $use_loopdev = 1;
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' ||
+                    $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') {
+               # do nothing
            } else {
-               update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
+               die "unsupported storage type '$scfg->{type}'\n";
            }
+           if ($mount_path) {
+               if ($format eq 'iso') {
+                   PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
+               } elsif ($isBase || defined($snapname)) {
+                   PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
+               } else {
+                   PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+               }
+           }
+           return wantarray ? ($path, $use_loopdev) : $path;
        } else {
-           die "implement me"
+           die "unsupported image format '$format'\n";
        }
-       PVE::LXC::write_config($vmid, $conf) if $running;
-    }
-
-    if ($running && scalar(@nohotplug)) {
-       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
+    } elsif ($type eq 'device') {
+       PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0) : $volid;
+    } elsif ($type eq 'bind') {
+       if ($mountpoint->{ro}) {
+           die "read-only bind mounts not supported\n";
+           # Theoretically we'd have to execute both:
+           # mount -o bind $a $b
+           # mount -o bind,remount,ro $a $b
+       }
+       die "directory '$volid' does not exist\n" if ! -d $volid;
+       &$check_mount_path($volid);
+       PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0) : $volid;
     }
+    
+    die "unsupported storage";
 }
 
-sub get_primary_ips {
-    my ($conf) = @_;
+sub get_vm_volumes {
+    my ($conf, $excludes) = @_;
 
-    # return data from net0
+    my $vollist = [];
 
-    my $net = $conf->{net0};
-    return undef if !$net;
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
 
-    my $ipv4 = $net->{ip};
-    $ipv4 =~ s!/\d+$!! if $ipv4;
-    my $ipv6 = $net->{ip};
-    $ipv6 =~ s!/\d+$!! if $ipv6;
+       return if $excludes && $ms eq $excludes;
 
-    return ($ipv4, $ipv6);
+       my $volid = $mountpoint->{volume};
+
+        return if !$volid || $mountpoint->{type} ne 'volume';
+
+        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+        return if !$sid;
+
+        push @$vollist, $volid;
+    });
+
+    return $vollist;
 }
 
-sub destory_lxc_container {
-    my ($storage_cfg, $vmid, $conf) = @_;
+sub mkfs {
+    my ($dev, $rootuid, $rootgid) = @_;
+
+    PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp',
+                            '-E', "root_owner=$rootuid:$rootgid",
+                            $dev]);
+}
+
+sub format_disk {
+    my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
 
-    if (my $volid = $conf->{'pve.volid'}) {
+    if ($volid =~ m!^/dev/.+!) {
+       mkfs($volid);
+       return;
+    }
 
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
-       die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-       PVE::Storage::vdisk_free($storage_cfg, $volid);
+    die "cannot format volume '$volid' with no storage\n" if !$storage;
 
-       destroy_config($vmid);
+    PVE::Storage::activate_volumes($storage_cfg, [$volid]);
 
-    } else {
-       my $cmd = ['lxc-destroy', '-n', $vmid ];
+    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, $rootuid, $rootgid);
+}
 
-       PVE::Tools::run_command($cmd);
+sub destroy_disks {
+    my ($storecfg, $vollist) = @_;
+
+    foreach my $volid (@$vollist) {
+       eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+       warn $@ if $@;
     }
 }
 
-my $safe_num_ne = sub {
-    my ($a, $b) = @_;
+sub create_disks {
+    my ($storecfg, $vmid, $settings, $conf) = @_;
 
-    return 0 if !defined($a) && !defined($b);
-    return 1 if !defined($a);
-    return 1 if !defined($b);
+    my $vollist = [];
 
-    return $a != $b;
-};
+    eval {
+       my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+       my $chown_vollist = [];
 
-my $safe_string_ne = sub {
-    my ($a, $b) = @_;
+       foreach_mountpoint($settings, sub {
+           my ($ms, $mountpoint) = @_;
 
-    return 0 if !defined($a) && !defined($b);
-    return 1 if !defined($a);
-    return 1 if !defined($b);
+           my $volid = $mountpoint->{volume};
+           my $mp = $mountpoint->{mp};
 
-    return $a ne $b;
-};
+           my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-sub update_net {
-    my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
+           if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
+               my ($storeid, $size_gb) = ($1, $2);
 
-    my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth . "p";
-    my $eth = $newnet->{name};
+               my $size_kb = int(${size_gb}*1024) * 1024;
 
-    if ($conf->{$opt}) {
-       if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
-           &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
+               my $scfg = PVE::Storage::storage_config($storecfg, $storage);
+               # fixme: use better naming ct-$vmid-disk-X.raw?
 
-            PVE::Network::veth_delete($veth);
-           delete $conf->{$opt};
-           PVE::LXC::write_config($vmid, $conf);
+               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, $rootuid, $rootgid);
+                   } else {
+                       $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                          undef, 0);
+                       push @$chown_vollist, $volid;
+                   }
+               } elsif ($scfg->{type} eq 'zfspool') {
 
-           hotplug_net($vmid, $conf, $opt, $newnet);
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                      undef, $size_kb);
+                   push @$chown_vollist, $volid;
+               } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') {
 
-       } 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})) {
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+                   format_disk($storecfg, $volid, $rootuid, $rootgid);
 
-               if ($conf->{$opt}->{bridge}){
-                   PVE::Network::tap_unplug($veth);
-                   delete $conf->{$opt}->{bridge};
-                   delete $conf->{$opt}->{tag};
-                   delete $conf->{$opt}->{firewall};
-                   PVE::LXC::write_config($vmid, $conf);
+               } 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, $rootuid, $rootgid);
+               } else {
+                   die "unable to create containers on storage type '$scfg->{type}'\n";
                }
+               push @$vollist, $volid;
+               $mountpoint->{volume} = $volid;
+               $mountpoint->{size} = $size_kb * 1024;
+               $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+           } else {
+                # use specified/existing volid/dir/device
+                $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+           }
+       });
 
-                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::Storage::activate_volumes($storecfg, $chown_vollist, undef);
+       foreach my $volid (@$chown_vollist) {
+           my $path = PVE::Storage::path($storecfg, $volid, undef);
+           chown($rootuid, $rootgid, $path);
        }
-    } else {
-       hotplug_net($vmid, $conf, $opt, $newnet);
+       PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef);
+    };
+    # free allocated images on error
+    if (my $err = $@) {
+       destroy_disks($storecfg, $vollist);
+        die $err;
     }
-
-    update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
+    return $vollist;
 }
 
-sub hotplug_net {
-    my ($vmid, $conf, $opt, $newnet) = @_;
+# bash completion helper
 
-    my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth . "p";
-    my $eth = $newnet->{name};
+sub complete_os_templates {
+    my ($cmdname, $pname, $cvalue) = @_;
 
-    PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
-    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+    my $cfg = PVE::Storage::config();
 
-    # attach peer in container
-    my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
-    PVE::Tools::run_command($cmd);
+    my $storeid;
 
-    # link up peer in container
-    $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up'  ];
-    PVE::Tools::run_command($cmd);
+    if ($cvalue =~ m/^([^:]+):/) {
+       $storeid = $1;
+    }
 
-    $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'};
+    my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
+    my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
 
-    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 $res = [];
+    foreach my $id (keys %$data) {
+       foreach my $item (@{$data->{$id}}) {
+           push @$res, $item->{volid} if defined($item->{volid});
+       }
+    }
 
-    PVE::LXC::write_config($vmid, $conf);
+    return $res;
 }
 
-sub update_ipconfig {
-    my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
+my $complete_ctid_full = sub {
+    my ($running) = @_;
 
-    my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
+    my $idlist = vmstatus();
 
-    my $optdata = $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 $active_hash = list_active_containers();
 
-    my $change_ip_config = sub {
-       my ($ipversion) = @_;
+    my $res = [];
 
-       my $family_opt = "-$ipversion";
-       my $suffix = $ipversion == 4 ? '' : $ipversion;
-       my $gw= "gw$suffix";
-       my $ip= "ip$suffix";
+    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;
 
-       my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
-       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
+    }
+    return $res;
+};
 
-       return if !$change_ip && !$change_gw;
+sub complete_ctid {
+    return &$complete_ctid_full();
+}
 
-       # 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 (my $err = $@) {
-               warn $err;
-               return;
-           }
-       }
+sub complete_ctid_stopped {
+    return &$complete_ctid_full(0);
+}
 
-       # step 2: replace gateway
-       #   If this fails we delete the added IP and cancel.
-       #   If it succeeds we save the config and delete the old IP, ignoring
-       #   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 (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); };
-                       warn $@ if $@; # no need to die here
-                   }
-                   return;
-               }
-           } else {
-               eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
-               # if the route was not deleted, the guest might have deleted it manually
-               # warn and continue
-               warn $@ if $@;
-           }
-       }
+sub complete_ctid_running {
+    return &$complete_ctid_full(1);
+}
 
-       # from this point on we safe the configuration
-       # step 3: delete old IP ignoring errors
-       if ($change_ip && $optdata->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
-           warn $@ if $@; # no need to die here
-       }
+sub parse_id_maps {
+    my ($conf) = @_;
 
-       foreach my $property ($ip, $gw) {
-           if ($newnet->{$property}) {
-               $optdata->{$property} = $newnet->{$property};
-           } else {
-               delete $optdata->{$property};
+    my $id_map = [];
+    my $rootuid = 0;
+    my $rootgid = 0;
+
+    my $lxc = $conf->{lxc};
+    foreach my $entry (@$lxc) {
+       my ($key, $value) = @$entry;
+       next if $key ne 'lxc.id_map';
+       if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
+           my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
+           push @$id_map, [$type, $ct, $host, $length];
+           if ($ct == 0) {
+               $rootuid = $host if $type eq 'u';
+               $rootgid = $host if $type eq 'g';
            }
+       } else {
+           die "failed to parse id_map: $value\n";
        }
-       PVE::LXC::write_config($vmid, $conf);
-       $lxc_setup->setup_network($conf);
-    };
+    }
 
-    &$change_ip_config(4);
-    &$change_ip_config(6);
+    if (!@$id_map && $conf->{unprivileged}) {
+       # Should we read them from /etc/subuid?
+       $id_map = [ ['u', '0', '100000', '65536'],
+                   ['g', '0', '100000', '65536'] ];
+       $rootuid = $rootgid = 100000;
+    }
+
+    return ($id_map, $rootuid, $rootgid);
+}
+
+sub userns_command {
+    my ($id_map) = @_;
+    if (@$id_map) {
+       return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];
+    }
+    return [];
 }
 
 1;