]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
add feature flags using apparmor profile generation
[pve-container.git] / src / PVE / LXC.pm
index 262865156cba385863e8d4ff864150c9e81c83a4..89f289e9113de6a4540d03e142807ed65cbfc3ab 100644 (file)
@@ -2,439 +2,38 @@ package PVE::LXC;
 
 use strict;
 use warnings;
+
 use POSIX qw(EINTR);
 
+use Socket;
+
 use File::Path;
-use Fcntl ':flock';
+use File::Spec;
+use Cwd qw();
+use Fcntl qw(O_RDONLY O_NOFOLLOW O_DIRECTORY);
+use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED);
+use IO::Socket::UNIX;
 
-use PVE::Cluster qw(cfs_register_file cfs_read_file);
+use PVE::Exception qw(raise_perm_exc);
 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 lock_file lock_file_full O_PATH);
+use PVE::CpuSet;
 use PVE::Network;
+use PVE::AccessControl;
+use PVE::ProcFSTools;
+use PVE::Syscall;
+use PVE::LXC::Config;
 
-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) = @_;
+use Time::HiRes qw (gettimeofday);
 
-    return $value if parse_lxc_network($value);
-
-    return undef if $noerr;
-
-    die "unable to parse network setting\n";
-}
+my $LXC_CONFIG_PATH = '/usr/share/lxc/config';
 
 my $nodename = PVE::INotify::nodename();
 
-sub parse_lxc_size {
-    my ($name, $value) = @_;
-
-    if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
-       my ($res, $unit) = ($1, lc($2 || 'b'));
-
-       return $res if $unit eq 'b';
-       return $res*1024 if $unit eq 'k';
-       return $res*1024*1024 if $unit eq 'm';
-       return $res*1024*1024*1024 if $unit eq 'g';
-    }
-
-    return undef;
-}
-
-my $valid_lxc_keys = {
-    'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
-    'lxc.include' => 1,
-    'lxc.rootfs' => 1,
-    'lxc.mount' => 1,
-    'lxc.utsname' => 1,
-
-    'lxc.id_map' => 1,
-
-    '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.console.logfile' => 1,
-    'lxc.devttydir' => 1,
-    'lxc.autodev' => 1,
-    'lxc.kmsg' => 1,
-    '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.hook.pre-start' => 1,
-    'lxc.hook.pre-mount' => 1,
-    'lxc.hook.mount' => 1,
-    'lxc.hook.autodev' => 1,
-    'lxc.hook.start' => 1,
-    'lxc.hook.post-stop' => 1,
-    'lxc.hook.clone' => 1,
-
-    # pve related keys
-    'pve.nameserver' => sub {
-       my ($name, $value) = @_;
-       return verify_nameserver_list($value);
-    },
-    'pve.searchdomain' => sub {
-       my ($name, $value) = @_;
-       return verify_searchdomain_list($value);
-    },
-    'pve.onboot' => '(0|1)',
-    'pve.startup' => sub {
-       my ($name, $value) = @_;
-       return PVE::JSONSchema::pve_verify_startup_order($value);
-    },
-    'pve.comment' => 1,
-    'pve.disksize' => '\d+(\.\d+)?',
-    'pve.volid' => sub {
-       my ($name, $value) = @_;
-       PVE::Storage::parse_volume_id($value);
-       return $value;
-    },
-
-     #pve snapshot
-    'pve.lock' => 1,
-    'pve.snaptime' => 1,
-    'pve.snapcomment' => 1,
-    'pve.parent' => 1,
-    'pve.snapstate' => 1,
-    'pve.snapname' => 1,
-};
-
-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 $valid_pve_network_keys = {
-    bridge => 1,
-    tag => 1,
-    firewall => 1,
-    ip => 1,
-    gw => 1,
-    ip6 => 1,
-    gw6 => 1,
-};
-
-my $lxc_array_configs = {
-    'lxc.network' => 1,
-    'lxc.mount' => 1,
-    'lxc.include' => 1,
-    'lxc.id_map' => 1,
-    'lxc.cgroup.devices.deny' => 1,
-};
-
-sub write_lxc_config {
-    my ($filename, $data) = @_;
-
-    my $raw = "";
-
-    return $raw if !$data;
-
-    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";
-       }
-    };
-
-    my $config_writer = sub {
-       my ($elem, $snapshot) = @_;
-
-       my $done_hash = { digest => 1};
-
-       if (defined(my $value = $elem->{'pve.snapname'})) {
-            &$dump_entry('pve.snapname', $value, $done_hash, $snapshot);
-       }
-
-       # Note: Order is important! Include defaults first, so that we
-       # can overwrite them later.
-       &$dump_entry('lxc.include', $elem->{'lxc.include'}, $done_hash, $snapshot);
-
-       foreach my $k (sort keys %$elem) {
-           next if $k !~ m/^lxc\./;
-           &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
-       }
-       foreach my $k (sort keys %$elem) {
-           next if $k !~ m/^pve\./;
-           &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
-       }
-       my $network_count = 0;
-
-       foreach my $k (sort keys %$elem) {
-           next if $k !~ m/^net\d+$/;
-           $done_hash->{$k} = 1;
-
-           my $net = $elem->{$k};
-           $network_count++;
-           $raw .= 'snap.' if $snapshot;
-           $raw .= "lxc.network.type = $net->{type}\n";
-           foreach my $subkey (sort keys %$net) {
-               next if $subkey eq 'type';
-               if ($valid_lxc_network_keys->{$subkey}) {
-                   $raw .= 'snap.' if $snapshot;
-                   $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
-               } elsif ($valid_pve_network_keys->{$subkey}) {
-                   $raw .= 'snap.' if $snapshot;
-                   $raw .= "pve.network.$subkey = $net->{$subkey}\n";
-               } else {
-                   die "found invalid network key '$subkey'";
-               }
-           }
-       }
-       if (!$network_count) {
-           $raw .= 'snap.' if $snapshot;
-           $raw .= "lxc.network.type = empty\n";
-       }
-       foreach my $k (sort keys %$elem) {
-           next if $k eq 'snapshots';
-           next if $done_hash->{$k};
-           die "found un-written value \"$k\" in config - implement this!";
-       }
-
-    };
-
-    &$config_writer($data);
-
-    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);
-       }
-    }
-
-    return $raw;
-}
-
-sub parse_lxc_option {
-    my ($name, $value) = @_;
-
-    my $parser = $valid_lxc_keys->{$name};
-
-    die "invalid key '$name'\n" if !defined($parser);
-
-    if ($parser eq '1') {
-       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 "unable to parse value '$value' for option '$name'\n";
-}
-
-sub parse_lxc_config {
-    my ($filename, $raw) = @_;
-
-    return undef if !defined($raw);
-
-    my $data = {
-       digest => Digest::SHA::sha1_hex($raw),
-    };
-
-    $filename =~ m|/lxc/(\d+)/config$|
-       || die "got strange filename '$filename'";
-
-    my $vmid = $1;
-
-    my $split_config = sub {
-       my ($raw) = @_;
-       my $sections = [];
-       my $tmp = '';
-       while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
-           my $line = $1;
-           if(!$line) {
-               push(@{$sections},$tmp);
-               $tmp = '';
-           } else {
-               $tmp .= "$line\n";
-           }
-       }
-       push(@{$sections},$tmp);
-
-       return $sections;
-    };
-
-    my $sec = &$split_config($raw);
-
-    foreach my  $sec_raw (@{$sec}){
-       next if $sec_raw eq '';
-       my $snapname = undef;
-
-       my $network_counter = 0;
-       my $network_list = [];
-       my $host_ifnames = {};
-
-       my $find_next_hostif_name = sub {
-           for (my $i = 0; $i < 10; $i++) {
-               my $name = "veth${vmid}.$i";
-               if (!$host_ifnames->{$name}) {
-                   $host_ifnames->{$name} = 1;
-                   return $name;
-               }
-           }
-
-           die "unable to find free host_ifname"; # should not happen
-       };
-
-       my $push_network = sub {
-           my ($netconf) = @_;
-           return if !$netconf;
-           push @{$network_list}, $netconf;
-           $network_counter++;
-           if (my $netname = $netconf->{'veth.pair'}) {
-               if ($netname =~ m/^veth(\d+).(\d)$/) {
-                   die "wrong vmid for network interface pair\n" if $1 != $vmid;
-                   my $host_ifnames->{$netname} = 1;
-               } else {
-                   die "wrong network interface pair\n";
-               }
-           }
-       };
-
-       my $network;
-
-       while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
-           my $line = $1;
-
-           next if $line =~ m/^\#/;
-           next if $line =~ m/^\s*$/;
-
-           if ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-               my ($subkey, $value) = ($2, $3);
-               if ($subkey eq 'type') {
-                   &$push_network($network);
-                   $network = { type => $value };
-               } elsif ($valid_lxc_network_keys->{$subkey}) {
-                   $network->{$subkey} = $value;
-               } else {
-                   die "unable to parse config line: $line\n";
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-               my ($subkey, $value) = ($2, $3);
-               if ($valid_pve_network_keys->{$subkey}) {
-                   $network->{$subkey} = $value;
-               } else {
-                   die "unable to parse config line: $line\n";
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) {
-               my ($name, $value) = ($2, $3);
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{$name} = $value;
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) {
-               if (!$snapname) {
-                   $snapname = $2;
-                   $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
-               } else {
-                   die "Configuarion broken\n";
-               }
-               next;
-           }
-           if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
-               my ($name, $value) = ($2, $3);
-
-               if ($lxc_array_configs->{$name}) {
-                   $data->{$name} = [] if !defined($data->{$name});
-                   if ($snapname) {
-                       push @{$data->{snapshots}->{$snapname}->{$name}},  parse_lxc_option($name, $value);
-                   } else {
-                       push @{$data->{$name}},  parse_lxc_option($name, $value);
-                   }
-               } else {
-                   if ($snapname) {
-                       die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
-                       $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
-                   } else {
-                       die "multiple definitions for $name\n" if defined($data->{$name});
-                       $data->{$name} = parse_lxc_option($name, $value);
-                   }
-               }
-
-               next;
-           }
-           die "unable to parse config line: $line\n";
-       }
-       &$push_network($network);
-
-       foreach my $net (@{$network_list}) {
-           next if $net->{type} eq 'empty'; # skip
-           $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
-           $net->{hwaddr} =  PVE::Tools::random_ether_addr() if !$net->{hwaddr};
-           die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
-
-           if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
-               if ($snapname) {
-                   $data->{snapshots}->{$snapname}->{"net$1"} = $net;
-               } else {
-                   $data->{"net$1"} = $net;
-               }
-           }
-       }
-    }
-
-    return $data;
-}
+my $cpuinfo= PVE::ProcFSTools::read_cpuinfo();
 
 sub config_list {
     my $vmlist = PVE::Cluster::get_vmlist();
@@ -447,378 +46,178 @@ sub config_list {
        my $d = $ids->{$vmid};
        next if !$d->{node} || $d->{node} ne $nodename;
        next if !$d->{type} || $d->{type} ne 'lxc';
-       $res->{$vmid}->{type} = 'lxc';
+       $res->{$vmid} = { type => 'lxc', vmid => $vmid };
     }
     return $res;
 }
 
-sub cfs_config_path {
-    my ($vmid, $node) = @_;
-
-    $node = $nodename if !$node;
-    return "nodes/$node/lxc/$vmid/config";
-}
-
-sub config_file {
-    my ($vmid, $node) = @_;
-
-    my $cfspath = cfs_config_path($vmid, $node);
-    return "/etc/pve/$cfspath";
-}
-
-sub load_config {
-    my ($vmid) = @_;
-
-    my $cfspath = cfs_config_path($vmid);
-
-    my $conf = PVE::Cluster::cfs_read_file($cfspath);
-    die "container $vmid does not exists\n" if !defined($conf);
-
-    return $conf;
-}
-
-sub create_config {
-    my ($vmid, $conf) = @_;
-
-    my $dir = "/etc/pve/nodes/$nodename/lxc";
-    mkdir $dir;
-
-    $dir .= "/$vmid";
-    mkdir($dir) || die "unable to create container configuration directory - $!\n";
-
-    write_config($vmid, $conf);
-}
-
 sub destroy_config {
     my ($vmid) = @_;
 
-    my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
-    File::Path::rmtree($dir);
+    unlink PVE::LXC::Config->config_file($vmid, $nodename);
 }
 
-sub write_config {
-    my ($vmid, $conf) = @_;
+# container status helpers
 
-    my $cfspath = cfs_config_path($vmid);
+sub list_active_containers {
 
-    PVE::Cluster::cfs_write_file($cfspath, $conf);
-}
+    my $filename = "/proc/net/unix";
 
-my $tempcounter = 0;
-sub write_temp_config {
-    my ($vmid, $conf) = @_;
+    # similar test is used by lcxcontainers.c: list_active_containers
+    my $res = {};
 
-    $tempcounter++;
-    my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
+    my $fh = IO::File->new ($filename, "r");
+    return $res if !$fh;
 
-    my $raw =  write_lxc_config($filename, $conf);
+    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;
+           }
+       }
+    }
 
-    PVE::Tools::file_set_contents($filename, $raw);
+    close($fh);
 
-    return $filename;
+    return $res;
 }
 
-# flock: we use one file handle per process, so lock file
-# can be called multiple times and succeeds for the same process.
-
-my $lock_handles =  {};
-my $lockdir = "/run/lock/lxc";
-
-sub lock_filename {
+# warning: this is slow
+sub check_running {
     my ($vmid) = @_;
 
-    return "$lockdir/pve-config-{$vmid}.lock";
-}
-
-sub lock_aquire {
-    my ($vmid, $timeout) = @_;
-
-    $timeout = 10 if !$timeout;
-    my $mode = LOCK_EX;
-
-    my $filename = lock_filename($vmid);
-
-    mkdir $lockdir if !-d $lockdir;
-
-    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};
-       }
+    my $active_hash = list_active_containers();
 
-       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 1 if defined($active_hash->{$vmid});
 
-           $lock_handles->{$$}->{$filename}->{refcount}++;
+    return undef;
+}
 
-           print STDERR " OK\n";
-       }
-    };
+sub get_container_disk_usage {
+    my ($vmid, $pid) = @_;
 
-    eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
-    my $err = $@;
-    if ($err) {
-       die "can't lock file '$filename' - $err";
-    }
+    return PVE::Tools::df("/proc/$pid/root/", 1);
 }
 
-sub lock_release {
-    my ($vmid) = @_;
+my $last_proc_vmid_stat;
 
-    my $filename = lock_filename($vmid);
+my $parse_cpuacct_stat = sub {
+    my ($vmid, $unprivileged) = @_;
 
-    if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
-       my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
-       if ($refcount <= 0) {
-           $lock_handles->{$$}->{$filename} = undef;
-           close ($fh);
-       }
-    }
-}
+    my $raw = read_cgroup_value('cpuacct', $vmid, $unprivileged, 'cpuacct.stat', 1);
 
-sub lock_container {
-    my ($vmid, $timeout, $code, @param) = @_;
+    my $stat = {};
 
-    my $res;
+    if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
 
-    lock_aquire($vmid, $timeout);
-    eval { $res = &$code(@param) };
-    my $err = $@;
-    lock_release($vmid);
+       $stat->{utime} = $1;
+       $stat->{stime} = $2;
 
-    die $err if $err;
+    }
 
-    return $res;
-}
+    return $stat;
+};
 
-my $confdesc = {
-    onboot => {
-       optional => 1,
-       type => 'boolean',
-       description => "Specifies whether a VM will be started during system bootup.",
-       default => 0,
+our $vmstatus_return_properties = {
+    vmid => get_standard_option('pve-vmid'),
+    status => {
+       description => "LXC Container status.",
+       type => 'string',
+       enum => ['stopped', 'running'],
     },
-    startup => get_standard_option('pve-startup-order'),
-    cpulimit => {
+    maxmem => {
+       description => "Maximum memory in bytes.",
+       type => 'integer',
        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,
+       renderer => 'bytes',
     },
-    cpuunits => {
-       optional => 1,
+    maxswap => {
+       description => "Maximum SWAP memory in bytes.",
        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,
+       renderer => 'bytes',
     },
-    swap => {
-       optional => 1,
+    maxdisk => {
+       description => "Root disk size in bytes.",
        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,
+       renderer => 'bytes',
     },
-    hostname => {
-       optional => 1,
-       description => "Set a host name for the container.",
+    name => {
+       description => "Container name.",
        type => 'string',
-       maxLength => 255,
-    },
-    description => {
        optional => 1,
-       type => 'string',
-       description => "Container description. Only used on the configuration web interface.",
     },
-    searchdomain => {
+    uptime => {
+       description => "Uptime.",
+       type => 'integer',
        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.",
+       renderer => 'duration',
     },
-    nameserver => {
+    cpus => {
+       description => "Maximum usable CPUs.",
+       type => 'number',
        optional => 1,
-       type => 'string',
-       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
     },
 };
 
-my $MAX_LXC_NETWORKS = 10;
-for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
-    $confdesc->{"net$i"} = {
-       optional => 1,
-       type => 'string', format => 'pve-lxc-network',
-       description => "Specifies network interfaces for the container.\n\n".
-           "The string should have the follow format:\n\n".
-           "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
-           "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
-           ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
-           ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
-    };
-}
-
-sub option_exists {
-    my ($name) = @_;
-
-    return defined($confdesc->{$name});
-}
-
-# add JSON properties for create and set function
-sub json_config_properties {
-    my $prop = shift;
-
-    foreach my $opt (keys %$confdesc) {
-       $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!^@/etc/pve/lxc/(\d+)/command$!) {
-               $res->{$1} = 1;
-           }
-       }
-    }
-
-    close($fh);
+sub vmstatus {
+    my ($opt_vmid) = @_;
 
-    return $res;
-}
-
-# warning: this is slow
-sub check_running {
-    my ($vmid) = @_;
+    my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc', vmid => $opt_vmid }} : config_list();
 
     my $active_hash = list_active_containers();
 
-    return 1 if defined($active_hash->{$vmid});
-
-    return undef;
-}
-
-sub get_container_disk_usage {
-    my ($vmid) = @_;
-
-    my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df',  '-P', '-B', '1', '/'];
-
-    my $res = {
-       total => 0,
-       used => 0,
-       avail => 0,
-    };
-
-    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,
-           };
-       }
-    };
-    eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
-    warn $@ if $@;
-
-    return $res;
-}
+    my $cpucount = $cpuinfo->{cpus} || 1;
 
-sub vmstatus {
-    my ($opt_vmid) = @_;
+    my $cdtime = gettimeofday;
 
-    my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
+    my $uptime = (PVE::ProcFSTools::read_proc_uptime(1))[0];
+    my $clock_ticks = POSIX::sysconf(&POSIX::_SC_CLK_TCK);
 
-    my $active_hash = list_active_containers();
+    my $unprivileged = {};
 
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
 
-       my $running = defined($active_hash->{$vmid});
+       eval { $d->{pid} = find_lxc_pid($vmid) if defined($active_hash->{$vmid}); };
+       warn $@ if $@; # ignore errors (consider them stopped)
 
-       $d->{status} = $running ? 'running' : 'stopped';
+       $d->{status} = $d->{pid} ? 'running' : 'stopped';
 
-       my $cfspath = cfs_config_path($vmid);
+       my $cfspath = PVE::LXC::Config->cfs_config_path($vmid);
        my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
 
-       $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
-       $d->{name} =~ s/[\s]//g;
+       $unprivileged->{$vmid} = $conf->{unprivileged};
 
-       $d->{cpus} = 0;
+       $d->{name} = $conf->{'hostname'} || "CT$vmid";
+       $d->{name} =~ s/[\s]//g;
 
-       my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
-       my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
+       $d->{cpus} = $conf->{cores} || $conf->{cpulimit};
+       $d->{cpus} = $cpucount if !$d->{cpus};
 
-       if ($cfs_period_us && $cfs_quota_us) {
-           $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
-       }
+       $d->{lock} = $conf->{lock} || '';
 
-       $d->{disk} = 0;
-       $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
-           int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
-
-       if (my $private = $conf->{'lxc.rootfs'}) {
-           if ($private =~ m!^/!) {
-               my $res = PVE::Tools::df($private, 2);
-               $d->{disk} = $res->{used};
-               $d->{maxdisk} = $res->{total};
-           } elsif ($running) {
-               if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
-                   my $res = get_container_disk_usage($vmid);
-                   $d->{disk} = $res->{used};
-                   $d->{maxdisk} = $res->{total};
-               }
+       if ($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 = PVE::LXC::Config->parse_ct_rootfs($rootfs);
+               $d->{maxdisk} = $rootinfo->{size} || (4*1024*1024*1024);
+           } else {
+               $d->{maxdisk} = 4*1024*1024*1024;
            }
        }
 
        $d->{mem} = 0;
        $d->{swap} = 0;
-       $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
-           ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
+       $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
+       $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
 
        $d->{uptime} = 0;
        $d->{cpu} = 0;
@@ -828,71 +227,110 @@ sub vmstatus {
 
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
+
+       $d->{template} = PVE::LXC::Config->is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
-       next if $d->{status} ne 'running';
+       my $pid = $d->{pid};
 
-       $d->{uptime} = 100; # fixme:
+       next if !$pid; # skip stopped CTs
 
-       $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 $proc_pid_stat = PVE::ProcFSTools::read_proc_pid_stat($pid);
+       $d->{uptime} = int(($uptime - $proc_pid_stat->{starttime}) / $clock_ticks); # the method lxcfs uses
 
-       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 $unpriv = $unprivileged->{$vmid};
+
+       if (-d '/sys/fs/cgroup/memory') {
+           my $memory_stat = read_cgroup_list('memory', $vmid, $unpriv, 'memory.stat');
+           my $mem_usage_in_bytes = read_cgroup_value('memory', $vmid, $unpriv, 'memory.usage_in_bytes');
+
+           $d->{mem} = $mem_usage_in_bytes - $memory_stat->{total_cache};
+           $d->{swap} = read_cgroup_value('memory', $vmid, $unpriv, 'memory.memsw.usage_in_bytes') - $mem_usage_in_bytes;
+       } else {
+           $d->{mem} = 0;
+           $d->{swap} = 0;
+       }
+
+       if (-d '/sys/fs/cgroup/blkio') {
+           my $blkio_bytes = read_cgroup_value('blkio', $vmid, $unpriv, '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';
+               }
            }
+       } else {
+           $d->{diskread} = 0;
+           $d->{diskwrite} = 0;
        }
-    }
 
-    return $list;
-}
+       if (-d '/sys/fs/cgroup/cpuacct') {
+           my $pstat = $parse_cpuacct_stat->($vmid, $unpriv);
 
+           my $used = $pstat->{utime} + $pstat->{stime};
 
-sub print_lxc_network {
-    my $net = shift;
+           my $old = $last_proc_vmid_stat->{$vmid};
+           if (!$old) {
+               $last_proc_vmid_stat->{$vmid} = {
+                   time => $cdtime,
+                   used => $used,
+                   cpu => 0,
+               };
+               next;
+           }
 
-    die "no network name defined\n" if !$net->{name};
+           my $dtime = ($cdtime -  $old->{time}) * $cpucount * $cpuinfo->{user_hz};
 
-    my $res = "name=$net->{name}";
+           if ($dtime > 1000) {
+               my $dutime = $used -  $old->{used};
 
-    foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
-       next if !defined($net->{$k});
-       $res .= ",$k=$net->{$k}";
+               $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus};
+               $last_proc_vmid_stat->{$vmid} = {
+                   time => $cdtime,
+                   used => $used,
+                   cpu => $d->{cpu},
+               };
+           } else {
+               $d->{cpu} = $old->{cpu};
+           }
+       } else {
+           $d->{cpu} = 0;
+       }
     }
 
-    return $res;
-}
+    my $netdev = PVE::ProcFSTools::read_proc_net_dev();
 
-sub parse_lxc_network {
-    my ($data) = @_;
+    foreach my $dev (keys %$netdev) {
+       next if $dev !~ m/^veth([1-9]\d*)i/;
+       my $vmid = $1;
+       my $d = $list->{$vmid};
 
-    my $res = {};
+       next if !$d;
 
-    return $res if !$data;
+       $d->{netout} += $netdev->{$dev}->{receive};
+       $d->{netin} += $netdev->{$dev}->{transmit};
 
-    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;
-       }
     }
 
-    $res->{type} = 'veth';
-    $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
+    return $list;
+}
 
-    return $res;
+sub read_cgroup_list($$$$) {
+    my ($group, $vmid, $unprivileged, $name) = @_;
+
+    my $content = read_cgroup_value($group, $vmid, $unprivileged, $name, 1);
+
+    return { split(/\s+/, $content) };
 }
 
-sub read_cgroup_value {
-    my ($group, $vmid, $name, $full) = @_;
+sub read_cgroup_value($$$$$) {
+    my ($group, $vmid, $unprivileged, $name, $full) = @_;
 
-    my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
+    my $nsdir = $unprivileged ? '' : 'ns/';
+    my $path = "/sys/fs/cgroup/$group/lxc/$vmid/${nsdir}$name";
 
     return PVE::Tools::file_get_contents($path) if $full;
 
@@ -919,7 +357,7 @@ sub find_lxc_console_pids {
 
        my @args = split(/\0/, $cmdline);
 
-       # serach for lxc-console -n <vmid>
+       # search for lxc-console -n <vmid>
        return if scalar(@args) != 3;
        return if $args[1] ne '-n';
        return if $args[2] !~ m/^\d+$/;
@@ -941,56 +379,20 @@ sub find_lxc_pid {
         my $line = shift;
         $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
     };
-    PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
+    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;
 }
 
-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',
-];
-
 # 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 => $ipv4_reverse_mask->[$2] };
+    if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) &&  ($2 <= 32)) {
+       return { address => $1, netmask => $PVE::Network::ipv4_reverse_mask->[$2] };
     }
 
     return undef if $noerr;
@@ -998,78 +400,242 @@ sub parse_ipv4_cidr {
     die "unable to parse ipv4 address/mask\n";
 }
 
-sub check_lock {
-    my ($conf) = @_;
-
-    die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
+sub get_cgroup_subsystems {
+       my $v1 = {};
+       my $v2 = 0;
+       my $data = PVE::Tools::file_get_contents('/proc/self/cgroup');
+       while ($data =~ /^\d+:([^:\n]*):.*$/gm) {
+               my $type = $1;
+               if (length($type)) {
+                       $v1->{$_} = 1 foreach split(/,/, $type);
+               } else {
+                       $v2 = 1;
+               }
+       }
+       return wantarray ? ($v1, $v2) : $v1;
 }
 
-sub lxc_conf_to_pve {
-    my ($vmid, $lxc_conf) = @_;
+# Currently we do not need to create seccomp profile 'files' as the only
+# choice our configuration actually allows is "with or without keyctl()",
+# so we distinguish between using lxc's "default" seccomp profile and our
+# added pve-userns.seccomp file.
+#
+# This returns a configuration line added to the raw lxc config.
+sub make_seccomp_config {
+    my ($conf, $unprivileged, $features) = @_;
+    # User-configured profile has precedence, note that the user's entry would
+    # be written 'after' this line anyway...
+    if (PVE::LXC::Config->has_lxc_entry($conf, 'lxc.seccomp.profile')) {
+       # Warn the user if this conflicts with a feature:
+       if ($features->{keyctl}) {
+           warn "explicitly configured lxc.seccomp.profile overrides the following settings: features:keyctl\n";
+       }
+       return '';
+    }
 
-    my $properties = json_config_properties();
+    # Privileged containers keep using the default (which is already part of
+    # the files included via lxc.include, so we don't need to write it out,
+    # that way it stays admin-configurable via /usr/share/lxc/config/... as
+    # well)
+    return '' if !$unprivileged;
 
-    my $conf = { digest => $lxc_conf->{digest} };
+    # Unprivileged containers will get keyctl() disabled by default as a
+    # workaround for systemd-networkd behavior. But we have an option to
+    # explicitly enable it:
+    return '' if $features->{keyctl};
 
-    foreach my $k (keys %$properties) {
+    # Finally we're in an unprivileged container without `keyctl` set
+    # explicitly. We have a file prepared for this:
+    return "lxc.seccomp.profile = $LXC_CONFIG_PATH/pve-userns.seccomp\n";
+}
 
-       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'};
+# Since lxc-3.0.2 we can have lxc generate a profile for the container
+# automatically. The default should be equivalent to the old
+# `lxc-container-default-cgns` profile.
+#
+# Additionally this also added `lxc.apparmor.raw` which can be used to inject
+# additional lines into the profile. We can use that to allow mounting specific
+# file systems.
+sub make_apparmor_config {
+    my ($conf, $unprivileged, $features) = @_;
+
+    # user-configured profile has precedence, but first we go through our own
+    # code to figure out whether we should warn the user:
+
+    my $raw = "lxc.apparmor.profile = generated\n";
+    my @profile_uses;
+
+    # There's lxc.apparmor.allow_nesting now, which will add the necessary
+    # apparmor lines, create an apparmor namespace for the container, but also
+    # adds proc and sysfs mounts to /dev/.lxc/{proc,sys}. These do not have
+    # lxcfs mounted over them, because that would prevent the container from
+    # mounting new instances of them for nested containers.
+    if ($features->{nesting}) {
+       push @profile_uses, 'features:nesting';
+       $raw .= "lxc.apparmor.allow_nesting = 1\n"
+    } else {
+       # In the default profile in /etc/apparmor.d we patch this in because
+       # otherwise a container can for example run `chown` on /sys, breaking
+       # access to it for non-CAP_DAC_OVERRIDE tools on the host:
+       $raw .= "lxc.apparmor.raw = deny mount -> /proc/,\n";
+       $raw .= "lxc.apparmor.raw = deny mount -> /sys/,\n";
+       # Preferably we could use the 'remount' flag but this does not sit well
+       # with apparmor_parser currently:
+       #  mount options=(rw, nosuid, nodev, noexec, remount) -> /sys/,
+    }
 
-           if ($cfs_period_us && $cfs_quota_us) {
-               $conf->{$k} = $cfs_quota_us/$cfs_period_us;
-           } else {
-               $conf->{$k} = 0;
-           }
-       } elsif ($k eq 'cpuunits') {
-           $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
-       } elsif ($k eq 'disk') {
-           $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
-               $lxc_conf->{'pve.disksize'} : 0;
-       } elsif ($k =~ m/^net\d$/) {
-           my $net = $lxc_conf->{$k};
-           next if !$net;
-           $conf->{$k} = print_lxc_network($net);
+    if (my $mount = $features->{mount}) {
+       push @profile_uses, 'features:mount';
+       foreach my $fs (PVE::Tools::split_list($mount)) {
+           $raw .= "lxc.apparmor.raw = mount fstype=$fs,\n";
        }
     }
 
-    if (my $parent = $lxc_conf->{'pve.parent'}) {
-           $conf->{parent} = $lxc_conf->{'pve.parent'};
+    # More to come?
+
+    if (PVE::LXC::Config->has_lxc_entry($conf, 'lxc.apparmor.profile')) {
+       if (length(my $used = join(', ', @profile_uses))) {
+           warn "explicitly configured lxc.apparmor.profile overrides the following settings: $used\n";
+       }
+       return '';
+    }
+
+    return $raw;
+}
+
+sub update_lxc_config {
+    my ($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 $custom_idmap = PVE::LXC::Config->has_lxc_entry($conf, 'lxc.idmap');
+    my $unprivileged = $conf->{unprivileged} || $custom_idmap;
+
+    my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
+
+    my $cfgpath = '/usr/share/lxc/config';
+    my $inc = "$cfgpath/$ostype.common.conf";
+    $inc ="$cfgpath/common.conf" if !-f $inc;
+    $raw .= "lxc.include = $inc\n";
+    if ($unprivileged) {
+       $inc = "$cfgpath/$ostype.userns.conf";
+       $inc = "$cfgpath/userns.conf" if !-f $inc;
+       $raw .= "lxc.include = $inc\n";
+    }
+
+    my $features = PVE::LXC::Config->parse_features($conf->{features});
+
+    $raw .= make_seccomp_config($conf, $unprivileged, $features);
+    $raw .= make_apparmor_config($conf, $unprivileged, $features);
+
+    # WARNING: DO NOT REMOVE this without making sure that loop device nodes
+    # cannot be exposed to the container with r/w access (cgroup perms).
+    # When this is enabled mounts will still remain in the monitor's namespace
+    # after the container unmounted them and thus will not detach from their
+    # files while the container is running!
+    $raw .= "lxc.monitor.unshare = 1\n";
+
+    my $cgv1 = get_cgroup_subsystems();
+
+    # Should we read them from /etc/subuid?
+    if ($unprivileged && !$custom_idmap) {
+       $raw .= "lxc.idmap = u 0 100000 65536\n";
+       $raw .= "lxc.idmap = g 0 100000 65536\n";
     }
 
-    if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
-       $conf->{description} = $lxc_conf->{'pve.snapcomment'};
+    if (!PVE::LXC::Config->has_dev_console($conf)) {
+       $raw .= "lxc.console.path = none\n";
+       $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n" if $cgv1->{devices};
     }
 
-    if (my $parent = $lxc_conf->{'pve.snaptime'}) {
-       $conf->{snaptime} = $lxc_conf->{'pve.snaptime'};
+    my $ttycount = PVE::LXC::Config->get_tty_count($conf);
+    $raw .= "lxc.tty.max = $ttycount\n";
+
+    # some init scripts expect a linux terminal (turnkey).
+    $raw .= "lxc.environment = TERM=linux\n";
+    
+    my $utsname = $conf->{hostname} || "CT$vmid";
+    $raw .= "lxc.uts.name = $utsname\n";
+
+    if ($cgv1->{memory}) {
+       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 ($cgv1->{cpu}) {
+       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";
+    }
+
+    die "missing 'rootfs' configuration\n"
+       if !defined($conf->{rootfs});
+
+    my $mountpoint = PVE::LXC::Config->parse_ct_rootfs($conf->{rootfs});
+
+    $raw .= "lxc.rootfs.path = $dir/rootfs\n";
+
+    foreach my $k (sort keys %$conf) {
+       next if $k !~ m/^net(\d+)$/;
+       my $ind = $1;
+       my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
+       $raw .= "lxc.net.$ind.type = veth\n";
+       $raw .= "lxc.net.$ind.veth.pair = veth${vmid}i${ind}\n";
+       $raw .= "lxc.net.$ind.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
+       $raw .= "lxc.net.$ind.name = $d->{name}\n" if defined($d->{name});
+       $raw .= "lxc.net.$ind.mtu = $d->{mtu}\n" if defined($d->{mtu});
+    }
+
+    if ($cgv1->{cpuset}) {
+       my $had_cpuset = 0;
+       if (my $lxcconf = $conf->{lxc}) {
+           foreach my $entry (@$lxcconf) {
+               my ($k, $v) = @$entry;
+               $had_cpuset = 1 if $k eq 'lxc.cgroup.cpuset.cpus';
+               $raw .= "$k = $v\n";
+           }
+       }
+
+       my $cores = $conf->{cores};
+       if (!$had_cpuset && $cores) {
+           my $cpuset = eval { PVE::CpuSet->new_from_cgroup('lxc', 'effective_cpus') };
+           $cpuset = PVE::CpuSet->new_from_cgroup('', 'effective_cpus') if !$cpuset;
+           my @members = $cpuset->members();
+           while (scalar(@members) > $cores) {
+               my $randidx = int(rand(scalar(@members)));
+               $cpuset->delete($members[$randidx]);
+               splice(@members, $randidx, 1); # keep track of the changes
+           }
+           $raw .= "lxc.cgroup.cpuset.cpus = ".$cpuset->short_string()."\n";
+       }
     }
 
-    return $conf;
+    File::Path::mkpath("$dir/rootfs");
+
+    PVE::Tools::file_set_contents("$dir/config", $raw);
 }
 
 # verify and cleanup nameserver list (replace \0 with ' ')
@@ -1097,115 +663,25 @@ sub verify_searchdomain_list {
     return join(' ', @list);
 }
 
-sub update_lxc_config {
-    my ($vmid, $conf, $running, $param, $delete) = @_;
+sub get_console_command {
+    my ($vmid, $conf, $noescapechar) = @_;
 
-    my @nohotplug;
+    my $cmode = PVE::LXC::Config->get_cmode($conf);
 
-    my $rootdir;
-    if ($running) {
-       my $pid = find_lxc_pid($vmid);
-       $rootdir = "/proc/$pid/root";
-    }
-
-    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;
-       }
-    }
-
-    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);
-
-       } 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);
-           }
-       } 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;
-           } else {
-               update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
-           }
-       } else {
-           die "implement me: $opt";
-       }
-       PVE::LXC::write_config($vmid, $conf) if $running;
+    my $cmd = [];
+    if ($cmode eq 'console') {
+       push @$cmd, 'lxc-console', '-n',  $vmid, '-t', 0;
+       push @$cmd, '-e', -1 if $noescapechar;
+    } elsif ($cmode eq 'tty') {
+       push @$cmd, 'lxc-console', '-n',  $vmid;
+       push @$cmd, '-e', -1 if $noescapechar;
+    } elsif ($cmode eq 'shell') {
+       push @$cmd, 'lxc-attach', '--clear-env', '-n', $vmid;
+    } else {
+       die "internal error";
     }
 
-    if ($running && scalar(@nohotplug)) {
-       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
-    }
+    return $cmd;
 }
 
 sub get_primary_ips {
@@ -1213,34 +689,70 @@ sub get_primary_ips {
 
     # return data from net0
 
-    my $net = $conf->{net0};
-    return undef if !$net;
+    return undef if !defined($conf->{net0});
+    my $net = PVE::LXC::Config->parse_lxc_network($conf->{net0});
 
     my $ipv4 = $net->{ip};
-    $ipv4 =~ s!/\d+$!! if $ipv4;
-    my $ipv6 = $net->{ip};
-    $ipv6 =~ s!/\d+$!! if $ipv6;
+    if ($ipv4) {
+       if ($ipv4 =~ /^(dhcp|manual)$/) {
+           $ipv4 = undef
+       } else {
+           $ipv4 =~ s!/\d+$!!;
+       }
+    }
+    my $ipv6 = $net->{ip6};
+    if ($ipv6) {
+       if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
+           $ipv6 = undef;
+       } else {
+           $ipv6 =~ s!/\d+$!!;
+       }
+    }
 
     return ($ipv4, $ipv6);
 }
 
-sub destory_lxc_container {
-    my ($storage_cfg, $vmid, $conf) = @_;
+sub delete_mountpoint_volume {
+    my ($storage_cfg, $vmid, $volume) = @_;
 
-    if (my $volid = $conf->{'pve.volid'}) {
+    return if PVE::LXC::Config->classify_mountpoint($volume) ne 'volume';
 
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
-       die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
+    my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
+    PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
+}
 
-       PVE::Storage::vdisk_free($storage_cfg, $volid);
+sub destroy_lxc_container {
+    my ($storage_cfg, $vmid, $conf, $replacement_conf) = @_;
 
-       destroy_config($vmid);
+    PVE::LXC::Config->foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+       delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
+    });
 
+    rmdir "/var/lib/lxc/$vmid/rootfs";
+    unlink "/var/lib/lxc/$vmid/config";
+    rmdir "/var/lib/lxc/$vmid";
+    if (defined $replacement_conf) {
+       PVE::LXC::Config->write_config($vmid, $replacement_conf);
     } else {
-       my $cmd = ['lxc-destroy', '-n', $vmid ];
-
-       PVE::Tools::run_command($cmd);
+       destroy_config($vmid);
     }
+
+    #my $cmd = ['lxc-destroy', '-n', $vmid ];
+    #PVE::Tools::run_command($cmd);
+}
+
+sub vm_stop_cleanup {
+    my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
+    
+    eval {
+       if (!$keepActive) {
+
+            my $vollist = PVE::LXC::Config->get_vm_volumes($conf);
+           PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
+       }
+    };
+    warn $@ if $@; # avoid errors - just warn
 }
 
 my $safe_num_ne = sub {
@@ -1266,54 +778,70 @@ my $safe_string_ne = sub {
 sub update_net {
     my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
 
-    my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth . "p";
+    if ($newnet->{type} ne 'veth') {
+       # for when there are physical interfaces
+       die "cannot update interface of type $newnet->{type}";
+    }
+
+    my $veth = "veth${vmid}i${netid}";
     my $eth = $newnet->{name};
 
-    if ($conf->{$opt}) {
-       if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
-           &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
+    if (my $oldnetcfg = $conf->{$opt}) {
+       my $oldnet = PVE::LXC::Config->parse_lxc_network($oldnetcfg);
+
+       if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
+           &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
 
-            PVE::Network::veth_delete($veth);
+           PVE::Network::veth_delete($veth);
            delete $conf->{$opt};
-           PVE::LXC::write_config($vmid, $conf);
+           PVE::LXC::Config->write_config($vmid, $conf);
 
-           hotplug_net($vmid, $conf, $opt, $newnet);
+           hotplug_net($vmid, $conf, $opt, $newnet, $netid);
 
-       } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
-                &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
-                &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
+       } else {
+           if (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+               &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
+               &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
 
-               if ($conf->{$opt}->{bridge}){
+               if ($oldnet->{bridge}) {
                    PVE::Network::tap_unplug($veth);
-                   delete $conf->{$opt}->{bridge};
-                   delete $conf->{$opt}->{tag};
-                   delete $conf->{$opt}->{firewall};
-                   PVE::LXC::write_config($vmid, $conf);
+                   foreach (qw(bridge tag firewall)) {
+                       delete $oldnet->{$_};
+                   }
+                   $conf->{$opt} = PVE::LXC::Config->print_lxc_network($oldnet);
+                   PVE::LXC::Config->write_config($vmid, $conf);
                }
 
-                PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
-               $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
-               $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
-               $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
-               PVE::LXC::write_config($vmid, $conf);
+               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
+               # This includes the rate:
+               foreach (qw(bridge tag firewall rate)) {
+                   $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
+               }
+           } elsif (&$safe_string_ne($oldnet->{rate}, $newnet->{rate})) {
+               # Rate can be applied on its own but any change above needs to
+               # include the rate in tap_plug since OVS resets everything.
+               PVE::Network::tap_rate_limit($veth, $newnet->{rate});
+               $oldnet->{rate} = $newnet->{rate}
+           }
+           $conf->{$opt} = PVE::LXC::Config->print_lxc_network($oldnet);
+           PVE::LXC::Config->write_config($vmid, $conf);
        }
     } else {
-       hotplug_net($vmid, $conf, $opt, $newnet);
+       hotplug_net($vmid, $conf, $opt, $newnet, $netid);
     }
 
     update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
 }
 
 sub hotplug_net {
-    my ($vmid, $conf, $opt, $newnet) = @_;
+    my ($vmid, $conf, $opt, $newnet, $netid) = @_;
 
-    my $veth = $newnet->{'veth.pair'};
+    my $veth = "veth${vmid}i${netid}";
     my $vethpeer = $veth . "p";
     my $eth = $newnet->{name};
 
     PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
-    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
 
     # attach peer in container
     my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
@@ -1323,34 +851,28 @@ sub hotplug_net {
     $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up'  ];
     PVE::Tools::run_command($cmd);
 
-    $conf->{$opt}->{type} = 'veth';
-    $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
-    $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
-    $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
-    $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
-    $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
-    $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
-
-    delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
-    delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
-    delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
-    delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
+    my $done = { type => 'veth' };
+    foreach (qw(bridge tag firewall hwaddr name)) {
+       $done->{$_} = $newnet->{$_} if $newnet->{$_};
+    }
+    $conf->{$opt} = PVE::LXC::Config->print_lxc_network($done);
 
-    PVE::LXC::write_config($vmid, $conf);
+    PVE::LXC::Config->write_config($vmid, $conf);
 }
 
 sub update_ipconfig {
     my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
 
-    my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
+    my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
 
-    my $optdata = $conf->{$opt};
+    my $optdata = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
     my $deleted = [];
     my $added = [];
-    my $netcmd = sub {
-       my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
-       PVE::Tools::run_command($cmd);
+    my $nscmd = sub {
+       my $cmdargs = shift;
+       PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
     };
+    my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
 
     my $change_ip_config = sub {
        my ($ipversion) = @_;
@@ -1360,14 +882,20 @@ sub update_ipconfig {
        my $gw= "gw$suffix";
        my $ip= "ip$suffix";
 
-       my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
-       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
+       my $newip = $newnet->{$ip};
+       my $newgw = $newnet->{$gw};
+       my $oldip = $optdata->{$ip};
+       my $oldgw = $optdata->{$gw};
+
+       my $change_ip = &$safe_string_ne($oldip, $newip);
+       my $change_gw = &$safe_string_ne($oldgw, $newgw);
 
        return if !$change_ip && !$change_gw;
 
        # step 1: add new IP, if this fails we cancel
-       if ($change_ip && $newnet->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
+       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;
@@ -1380,31 +908,55 @@ sub update_ipconfig {
        #   errors. The config is then saved.
        # Note: 'ip route replace' can add
        if ($change_gw) {
-           if ($newnet->{$gw}) {
-               eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
+           if ($newgw) {
+               eval {
+                   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 { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
+                       eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
                        warn $@ if $@; # no need to die here
                    }
                    return;
                }
            } else {
-               eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
+               eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
                # if the route was not deleted, the guest might have deleted it manually
                # warn and continue
                warn $@ if $@;
            }
+           if ($oldgw && $oldip && !PVE::Network::is_ip_in_cidr($oldgw, $oldip)) {
+               eval { &$ipcmd($family_opt, 'route', 'del', $oldgw, 'dev', $eth); };
+               # warn if the route was deleted manually
+               warn $@ if $@;
+           }
        }
 
-       # from this point on we safe the configuration
+       # from this point on we save the configuration
        # step 3: delete old IP ignoring errors
-       if ($change_ip && $optdata->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
+       if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
+           # We need to enable promote_secondaries, otherwise our newly added
+           # address will be removed along with the old one.
+           my $promote = 0;
+           eval {
+               if ($ipversion == 4) {
+                   &$nscmd({ outfunc => sub { $promote = int(shift) } },
+                           'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
+                   &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
+               }
+               &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
+           };
            warn $@ if $@; # no need to die here
+
+           if ($ipversion == 4) {
+               &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
+           }
        }
 
        foreach my $property ($ip, $gw) {
@@ -1414,7 +966,8 @@ sub update_ipconfig {
                delete $optdata->{$property};
            }
        }
-       PVE::LXC::write_config($vmid, $conf);
+       $conf->{$opt} = PVE::LXC::Config->print_lxc_network($optdata);
+       PVE::LXC::Config->write_config($vmid, $conf);
        $lxc_setup->setup_network($conf);
     };
 
@@ -1423,194 +976,924 @@ sub update_ipconfig {
 
 }
 
-# Internal snapshots
+my $enter_namespace = sub {
+    my ($vmid, $pid, $which, $type) = @_;
+    sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
+       or die "failed to open $which namespace of container $vmid: $!\n";
+    PVE::Tools::setns(fileno($fd), $type)
+       or die "failed to enter $which namespace of container $vmid: $!\n";
+    close $fd;
+};
+
+my $do_syncfs = sub {
+    my ($vmid, $pid, $socket) = @_;
 
-# NOTE: Snapshot create/delete involves several non-atomic
-# action, and can take a long time.
-# So we try to avoid locking the file and use 'lock' variable
-# inside the config file instead.
+    &$enter_namespace($vmid, $pid, 'mnt', PVE::Tools::CLONE_NEWNS);
 
-my $snapshot_copy_config = sub {
-    my ($source, $dest) = @_;
+    # Tell the parent process to start reading our /proc/mounts
+    print {$socket} "go\n";
+    $socket->flush();
 
-    foreach my $k (keys %$source) {
-       next if $k eq 'snapshots';
-       next if $k eq 'pve.snapstate';
-       next if $k eq 'pve.snaptime';
-       next if $k eq 'pve.lock';
-       next if $k eq 'digest';
-       next if $k eq 'pve.comment';
+    # Receive /proc/self/mounts
+    my $mountdata = do { local $/ = undef; <$socket> };
+    close $socket;
 
-       $dest->{$k} = $source->{$k};
+    # Now sync all mountpoints...
+    my $mounts = PVE::ProcFSTools::parse_mounts($mountdata);
+    foreach my $mp (@$mounts) {
+       my ($what, $dir, $fs) = @$mp;
+       next if $fs eq 'fuse.lxcfs';
+       eval { PVE::Tools::sync_mountpoint($dir); };
+       warn $@ if $@;
     }
 };
 
-my $snapshot_prepare = sub {
-    my ($vmid, $snapname, $comment) = @_;
+sub sync_container_namespace {
+    my ($vmid) = @_;
+    my $pid = find_lxc_pid($vmid);
+
+    # SOCK_DGRAM is nicer for barriers but cannot be slurped
+    socketpair my $pfd, my $cfd, AF_UNIX, SOCK_STREAM, PF_UNSPEC
+       or die "failed to create socketpair: $!\n";
+
+    my $child = fork();
+    die "fork failed: $!\n" if !defined($child);
+
+    if (!$child) {
+       eval {
+           close $pfd;
+           &$do_syncfs($vmid, $pid, $cfd);
+       };
+       if (my $err = $@) {
+           warn $err;
+           POSIX::_exit(1);
+       }
+       POSIX::_exit(0);
+    }
+    close $cfd;
+    my $go = <$pfd>;
+    die "failed to enter container namespace\n" if $go ne "go\n";
+
+    open my $mounts, '<', "/proc/$child/mounts"
+       or die "failed to open container's /proc/mounts: $!\n";
+    my $mountdata = do { local $/ = undef; <$mounts> };
+    close $mounts;
+    print {$pfd} $mountdata;
+    close $pfd;
+
+    while (waitpid($child, 0) != $child) {}
+    die "failed to sync container namespace\n" if $? != 0;
+}
 
-    my $snap;
+sub template_create {
+    my ($vmid, $conf) = @_;
 
-    my $updatefn =  sub {
+    my $storecfg = PVE::Storage::config();
 
-       my $conf = load_config($vmid);
+    PVE::LXC::Config->foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
 
-       check_lock($conf);
+       my $volid = $mountpoint->{volume};
 
-       $conf->{'pve.lock'} = 'snapshot';
+       die "Template feature is not available for '$volid'\n"
+           if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
+    });
 
-       die "snapshot name '$snapname' already used\n"
-           if defined($conf->{snapshots}->{$snapname});
+    PVE::LXC::Config->foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
 
-       my $storecfg = PVE::Storage::config();
-       die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
+       my $volid = $mountpoint->{volume};
 
-       $snap = $conf->{snapshots}->{$snapname} = {};
+       PVE::Storage::activate_volumes($storecfg, [$volid]);
 
-       &$snapshot_copy_config($conf, $snap);
+       my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
+       $mountpoint->{volume} = $template_volid;
+       $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq "rootfs");
+    });
 
-       $snap->{'pve.snapstate'} = "prepare";
-       $snap->{'pve.snaptime'} = time();
-       $snap->{'pve.snapname'} = $snapname;
-       $snap->{'pve.snapcomment'} = $comment if $comment;
-       $conf->{snapshots}->{$snapname} = $snap;
+    PVE::LXC::Config->write_config($vmid, $conf);
+}
 
-       PVE::LXC::write_config($vmid, $conf);
+sub check_ct_modify_config_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    my $check = sub {
+       my ($opt, $delete) = @_;
+       if ($opt eq 'cores' || $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']);
+           return if $delete;
+           my $data = $opt eq 'rootfs' ? PVE::LXC::Config->parse_ct_rootfs($newconf->{$opt})
+                                       : PVE::LXC::Config->parse_ct_mountpoint($newconf->{$opt});
+           raise_perm_exc("mount point type $data->{type} is only allowed for root\@pam")
+               if $data->{type} ne 'volume';
+       } 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']);
+       } elsif ($opt eq 'features') {
+           # For now this is restricted to root@pam
+           raise_perm_exc("changing feature flags is only allowed for root\@pam");
+       } else {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
+       }
     };
 
-    lock_container($vmid, 10, $updatefn);
+    foreach my $opt (keys %$newconf) {
+       &$check($opt, 0);
+    }
+    foreach my $opt (@$delete) {
+       &$check($opt, 1);
+    }
+
+    return 1;
+}
 
-    return $snap;
-};
+sub umount_all {
+    my ($vmid, $storage_cfg, $conf, $noerr) = @_;
+
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    my $volid_list = PVE::LXC::Config->get_vm_volumes($conf);
+
+    PVE::LXC::Config->foreach_mountpoint_reverse($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       my $volid = $mountpoint->{volume};
+       my $mount = $mountpoint->{mp};
+
+       return if !$volid || !$mount;
 
-my $snapshot_commit = sub {
-    my ($vmid, $snapname) = @_;
+       my $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
 
-    my $updatefn = sub {
+       return if !PVE::ProcFSTools::is_mounted($mount_path);
 
-       my $conf = load_config($vmid);
+       eval {
+           PVE::Tools::run_command(['umount', '-d', $mount_path]);
+       };
+       if (my $err = $@) {
+           if ($noerr) {
+               warn $err;
+           } else {
+               die $err;
+           }
+       }
+    });
+}
 
-       die "missing snapshot lock\n"
-           if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
+sub mount_all {
+    my ($vmid, $storage_cfg, $conf, $ignore_ro) = @_;
 
-       die "snapshot '$snapname' does not exist\n" 
-           if !defined($conf->{snapshots}->{$snapname});
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    File::Path::make_path($rootdir);
 
-       die "wrong snapshot state\n"
-           if !($conf->{snapshots}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots}->{$snapname}->{'pve.snapstate'} eq "prepare");
+    my $volid_list = PVE::LXC::Config->get_vm_volumes($conf);
+    PVE::Storage::activate_volumes($storage_cfg, $volid_list);
 
-       delete $conf->{snapshots}->{$snapname}->{'pve.snapstate'};
-       delete $conf->{'pve.lock'};
-       $conf->{'pve.parent'} = $snapname;
+    eval {
+       PVE::LXC::Config->foreach_mountpoint($conf, sub {
+           my ($ms, $mountpoint) = @_;
 
-       PVE::LXC::write_config($vmid, $conf);
+           $mountpoint->{ro} = 0 if $ignore_ro;
 
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+        });
     };
+    if (my $err = $@) {
+       warn "mounting container failed\n";
+       umount_all($vmid, $storage_cfg, $conf, 1);
+       die $err;
+    }
 
-    lock_container($vmid, 10 ,$updatefn);
-};
+    return $rootdir;
+}
+
+
+sub mountpoint_mount_path {
+    my ($mountpoint, $storage_cfg, $snapname) = @_;
+
+    return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
+}
+
+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 has_feature {
-    my ($feature, $conf, $storecfg, $snapname) = @_;
-    #Fixme add other drives if necessary.
-    my $err;
-    my $volid = $conf->{'pve.volid'};
-    $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname);
+# Run a function with a file attached to a loop device.
+# The loop device is always detached afterwards (or set to autoclear).
+# Returns the loop device.
+sub run_with_loopdev {
+    my ($func, $file) = @_;
+    my $device = query_loopdev($file);
+    # Try to reuse an existing device
+    if ($device) {
+       # We assume that whoever setup the loop device is responsible for
+       # detaching it.
+       &$func($device);
+       return $device;
+    }
 
-    return $err ? 0 : 1;
+    my $parser = sub {
+       my $line = shift;
+       if ($line =~ m@^(/dev/loop\d+)$@) {
+           $device = $1;
+       }
+    };
+    PVE::Tools::run_command(['losetup', '--show', '-f', $file], outfunc => $parser);
+    die "failed to setup loop device for $file\n" if !$device;
+    eval { &$func($device); };
+    my $err = $@;
+    PVE::Tools::run_command(['losetup', '-d', $device]);
+    die $err if $err;
+    return $device;
 }
 
-sub snapshot_create {
-    my ($vmid, $snapname, $comment) = @_;
+# In scalar mode: returns a file handle to the deepest directory node.
+# In list context: returns a list of:
+#   * the deepest directory node
+#   * the 2nd deepest directory (parent of the above)
+#   * directory name of the last directory
+# So that the path $2/$3 should lead to $1 afterwards.
+sub walk_tree_nofollow($$$) {
+    my ($start, $subdir, $mkdir) = @_;
+
+    # splitdir() returns '' for empty components including the leading /
+    my @comps = grep { length($_)>0 } File::Spec->splitdir($subdir);
+
+    sysopen(my $fd, $start, O_PATH | O_DIRECTORY)
+       or die "failed to open start directory $start: $!\n";
+
+    my $dir = $start;
+    my $last_component = undef;
+    my $second = $fd;
+    foreach my $component (@comps) {
+       $dir .= "/$component";
+       my $next = PVE::Tools::openat(fileno($fd), $component, O_NOFOLLOW | O_DIRECTORY);
+
+       if (!$next) {
+           # failed, check for symlinks and try to create the path
+           die "symlink encountered at: $dir\n" if $! == ELOOP || $! == ENOTDIR;
+           die "cannot open directory $dir: $!\n" if !$mkdir;
+
+           # We don't check for errors on mkdirat() here and just try to
+           # openat() again, since at least one error (EEXIST) is an
+           # expected possibility if multiple containers start
+           # simultaneously. If someone else injects a symlink now then
+           # the subsequent openat() will fail due to O_NOFOLLOW anyway.
+           PVE::Tools::mkdirat(fileno($fd), $component, 0755);
+
+           $next = PVE::Tools::openat(fileno($fd), $component, O_NOFOLLOW | O_DIRECTORY);
+           die "failed to create path: $dir: $!\n" if !$next;
+       }
 
-    my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
+       close $second if defined($last_component);
+       $last_component = $component;
+       $second = $fd;
+       $fd = $next;
+    }
 
-    my $config = load_config($vmid);
+    return ($fd, defined($last_component) && $second, $last_component) if wantarray;
+    close $second if defined($last_component);
+    return $fd;
+}
 
-    my $cmd = "/usr/bin/lxc-freeze -n $vmid";
-    my $running = check_running($vmid);
-    eval {
-       if ($running) {
-           PVE::Tools::run_command($cmd);
-       };
+# To guard against symlink attack races against other currently running
+# containers with shared recursive bind mount hierarchies we prepare a
+# directory handle for the directory we're mounting over to verify the
+# mountpoint afterwards.
+sub __bindmount_prepare {
+    my ($hostroot, $dir) = @_;
+    my $srcdh = walk_tree_nofollow($hostroot, $dir, 0);
+    return $srcdh;
+}
 
-       my $storecfg = PVE::Storage::config();
-       my $volid = $config->{'pve.volid'};
+# Assuming we mount to rootfs/a/b/c, verify with the directory handle to 'b'
+# ($parentfd) that 'b/c' (openat($parentfd, 'c')) really leads to the directory
+# we intended to bind mount.
+sub __bindmount_verify {
+    my ($srcdh, $parentfd, $last_dir, $ro) = @_;
+    my $destdh;
+    if ($parentfd) {
+       # Open the mount point path coming from the parent directory since the
+       # filehandle we would have gotten as first result of walk_tree_nofollow
+       # earlier is still a handle to the underlying directory instead of the
+       # mounted path.
+       $destdh = PVE::Tools::openat(fileno($parentfd), $last_dir, PVE::Tools::O_PATH | O_NOFOLLOW | O_DIRECTORY);
+       die "failed to open mount point: $!\n" if !$destdh;
+       if ($ro) {
+           my $dot = '.';
+           # no separate function because 99% of the time it's the wrong thing to use.
+           if (syscall(PVE::Syscall::faccessat, fileno($destdh), $dot, &POSIX::W_OK, 0) != -1) {
+               die "failed to mark bind mount read only\n";
+           }
+           die "read-only check failed: $!\n" if $! != EROFS;
+       }
+    } else {
+       # For the rootfs we don't have a parentfd so we open the path directly.
+       # Note that this means bindmounting any prefix of the host's
+       # /var/lib/lxc/$vmid path into another container is considered a grave
+       # security error.
+       sysopen $destdh, $last_dir, O_PATH | O_DIRECTORY;
+       die "failed to open mount point: $!\n" if !$destdh;
+    }
 
-       $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
-       if ($running) {
-           PVE::Tools::run_command($cmd);
-       };
+    my ($srcdev, $srcinode) = stat($srcdh);
+    my ($dstdev, $dstinode) = stat($destdh);
+    close $srcdh;
+    close $destdh;
 
-       PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
-       &$snapshot_commit($vmid, $snapname);
-    };
-    if(my $err = $@) {
-       snapshot_delete($vmid, $snapname, 1);
-       die "$err\n";
+    return ($srcdev == $dstdev && $srcinode == $dstinode);
+}
+
+# Perform the actual bind mounting:
+sub __bindmount_do {
+    my ($dir, $dest, $ro, @extra_opts) = @_;
+    PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
+    if ($ro) {
+       eval { PVE::Tools::run_command(['mount', '-o', 'bind,remount,ro', $dest]); };
+       if (my $err = $@) {
+           warn "bindmount error\n";
+           # don't leave writable bind-mounts behind...
+           PVE::Tools::run_command(['umount', $dest]);
+           die $err;
+       }
     }
 }
 
-sub snapshot_delete {
-    my ($vmid, $snapname, $force) = @_;
+sub bindmount {
+    my ($dir, $parentfd, $last_dir, $dest, $ro, @extra_opts) = @_;
+
+    my $srcdh = __bindmount_prepare('/', $dir);
 
-    my $snap;
+    __bindmount_do($dir, $dest, $ro, @extra_opts);
 
-    my $conf;
+    if (!__bindmount_verify($srcdh, $parentfd, $last_dir, $ro)) {
+       PVE::Tools::run_command(['umount', $dest]);
+       die "detected mount path change at: $dir\n";
+    }
+}
 
-    my $updatefn =  sub {
+# Cleanup $rootdir a bit (double and trailing slashes), build the mount path
+# from $rootdir and $mount and walk the path from $rootdir to the final
+# directory to check for symlinks.
+sub __mount_prepare_rootdir {
+    my ($rootdir, $mount) = @_;
+    $rootdir =~ s!/+!/!g;
+    $rootdir =~ s!/+$!!;
+    my $mount_path = "$rootdir/$mount";
+    my ($mpfd, $parentfd, $last_dir) = walk_tree_nofollow($rootdir, $mount, 1);
+    return ($rootdir, $mount_path, $mpfd, $parentfd, $last_dir);
+}
 
-       $conf = load_config($vmid);
+# 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};
+    my $quota = !$snapname && !$mountpoint->{ro} && $mountpoint->{quota};
+    my $mounted_dev;
+    
+    return if !$volid || !$mount;
+
+    $mount =~ s!/+!/!g;
+
+    my $mount_path;
+    my ($mpfd, $parentfd, $last_dir);
+    
+    if (defined($rootdir)) {
+       ($rootdir, $mount_path, $mpfd, $parentfd, $last_dir) =
+           __mount_prepare_rootdir($rootdir, $mount);
+    }
+    
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-       $snap = $conf->{snapshots}->{$snapname};
+    die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
 
-       check_lock($conf);
+    my $optstring = '';
+    my $acl = $mountpoint->{acl};
+    if (defined($acl)) {
+       $optstring .= ($acl ? 'acl' : 'noacl');
+    }
+    my $readonly = $mountpoint->{ro};
 
-       die "snapshot '$snapname' does not exist\n" if !defined($snap);
+    my @extra_opts;
+    @extra_opts = ('-o', $optstring) if $optstring;
 
-       $snap->{'pve.snapstate'} = 'delete';
+    if ($storage) {
 
-       PVE::LXC::write_config($vmid, $conf);
-    };
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
 
-    lock_container($vmid, 10, $updatefn);
+       # early sanity checks:
+       # we otherwise call realpath on the rbd url
+       die "containers on rbd storage without krbd are not supported\n"
+           if $scfg->{type} eq 'rbd' && !$scfg->{krbd};
 
-    my $storecfg = PVE::Storage::config();
+       my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
 
-    my $del_snap =  sub {
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
 
-       check_lock($conf);
+       $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
 
-       if ($conf->{'pve.parent'} eq $snapname) {
-           if ($conf->{snapshots}->{$snapname}->{'pve.snapname'}) {
-               $conf->{'pve.parent'} = $conf->{snapshots}->{$snapname}->{'pve.parent'};
+       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 (defined($acl) && $scfg->{type} eq 'zfspool') {
+                       my $acltype = ($acl ? 'acltype=posixacl' : 'acltype=noacl');
+                       my (undef, $name) = PVE::Storage::parse_volname($storage_cfg, $volid);
+                       $name .= "\@$snapname" if defined($snapname);
+                       PVE::Tools::run_command(['zfs', 'set', $acltype, "$scfg->{pool}/$name"]);
+                   }
+                   bindmount($path, $parentfd, $last_dir//$rootdir, $mount_path, $readonly, @extra_opts);
+                   warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
+               }
+           }
+           return wantarray ? ($path, 0, undef) : $path;
+       } elsif ($format eq 'raw' || $format eq 'iso') {
+           # NOTE: 'mount' performs canonicalization without the '-c' switch, which for
+           # device-mapper devices is special-cased to use the /dev/mapper symlinks.
+           # Our autodev hook expects the /dev/dm-* device currently
+           # and will create the /dev/mapper symlink accordingly
+           $path = Cwd::realpath($path);
+           die "failed to get device path\n" if !$path;
+           ($path) = ($path =~ /^(.*)$/s); #untaint
+           my $domount = sub {
+               my ($path) = @_;
+               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 {
+                       if ($quota) {
+                           push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
+                       }
+                       push @extra_opts, '-o', 'ro' if $readonly;
+                       PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+                   }
+               }
+           };
+           my $use_loopdev = 0;
+           if ($scfg->{path}) {
+               $mounted_dev = run_with_loopdev($domount, $path);
+               $use_loopdev = 1;
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' ||
+                    $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') {
+               $mounted_dev = $path;
+               &$domount($path);
            } else {
-               delete $conf->{'pve.parent'};
+               die "unsupported storage type '$scfg->{type}'\n";
            }
+           return wantarray ? ($path, $use_loopdev, $mounted_dev) : $path;
+       } else {
+           die "unsupported image format '$format'\n";
        }
+    } elsif ($type eq 'device') {
+       push @extra_opts, '-o', 'ro' if $readonly;
+       push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0' if $quota;
+       # See the NOTE above about devicemapper canonicalization
+       my ($devpath) = (Cwd::realpath($volid) =~ /^(.*)$/s); # realpath() taints
+       PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0, $devpath) : $volid;
+    } elsif ($type eq 'bind') {
+       die "directory '$volid' does not exist\n" if ! -d $volid;
+       bindmount($volid, $parentfd, $last_dir//$rootdir, $mount_path, $readonly, @extra_opts) if $mount_path;
+       warn "cannot enable quota control for bind mounts\n" if $quota;
+       return wantarray ? ($volid, 0, undef) : $volid;
+    }
+    
+    die "unsupported storage";
+}
+
+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 ($volid =~ m!^/dev/.+!) {
+       mkfs($volid);
+       return;
+    }
+
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-       delete $conf->{snapshots}->{$snapname};
+    die "cannot format volume '$volid' with no storage\n" if !$storage;
 
-       PVE::LXC::write_config($vmid, $conf);
+    PVE::Storage::activate_volumes($storage_cfg, [$volid]);
+
+    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);
+}
+
+sub destroy_disks {
+    my ($storecfg, $vollist) = @_;
+
+    foreach my $volid (@$vollist) {
+       eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+       warn $@ if $@;
+    }
+}
+
+sub alloc_disk {
+    my ($storecfg, $vmid, $storage, $size_kb, $rootuid, $rootgid) = @_;
+
+    my $needs_chown = 0;
+    my $volid;
+
+    my $scfg = PVE::Storage::storage_config($storecfg, $storage);
+    # fixme: use better naming ct-$vmid-disk-X.raw?
+
+    eval {
+       my $do_format = 0;
+       if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || $scfg->{type} eq 'cifs' ) {
+           if ($size_kb > 0) {
+               $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
+                                                  undef, $size_kb);
+               $do_format = 1;
+           } else {
+               $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                  undef, 0);
+               $needs_chown = 1;
+           }
+       } elsif ($scfg->{type} eq 'zfspool') {
+
+           $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                              undef, $size_kb);
+           $needs_chown = 1;
+       } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') {
+
+           $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+           $do_format = 1;
+
+       } 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);
+           $do_format = 1;
+       } else {
+           die "unable to create containers on storage type '$scfg->{type}'\n";
+       }
+       format_disk($storecfg, $volid, $rootuid, $rootgid) if $do_format;
     };
+    if (my $err = $@) {
+       # in case formatting got interrupted:
+       if (defined($volid)) {
+           eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+           warn $@ if $@;
+       }
+       die $err;
+    }
 
-    my $volid = $conf->{snapshots}->{$snapname}->{'pve.volid'};
+    return ($volid, $needs_chown);
+}
+
+our $NEW_DISK_RE = qr/^([^:\s]+):(\d+(\.\d+)?)$/;
+sub create_disks {
+    my ($storecfg, $vmid, $settings, $conf) = @_;
+
+    my $vollist = [];
 
     eval {
-       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
+       my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+       my $chown_vollist = [];
+
+       PVE::LXC::Config->foreach_mountpoint($settings, sub {
+           my ($ms, $mountpoint) = @_;
+
+           my $volid = $mountpoint->{volume};
+           my $mp = $mountpoint->{mp};
+
+           my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+           if ($storage && ($volid =~ $NEW_DISK_RE)) {
+               my ($storeid, $size_gb) = ($1, $2);
+
+               my $size_kb = int(${size_gb}*1024) * 1024;
+
+               my $needs_chown = 0;
+               ($volid, $needs_chown) = alloc_disk($storecfg, $vmid, $storage, $size_kb, $rootuid, $rootgid);
+               push @$chown_vollist, $volid if $needs_chown;
+               push @$vollist, $volid;
+               $mountpoint->{volume} = $volid;
+               $mountpoint->{size} = $size_kb * 1024;
+               $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+           } else {
+                # use specified/existing volid/dir/device
+                $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+           }
+       });
+
+       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);
+       }
+       PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef);
     };
-    my $err = $@;
+    # free allocated images on error
+    if (my $err = $@) {
+       destroy_disks($storecfg, $vollist);
+        die $err;
+    }
+    return $vollist;
+}
+
+# bash completion helper
+
+sub complete_os_templates {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Storage::config();
+
+    my $storeid;
+
+    if ($cvalue =~ m/^([^:]+):/) {
+       $storeid = $1;
+    }
+
+    my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
+    my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
+
+    my $res = [];
+    foreach my $id (keys %$data) {
+       foreach my $item (@{$data->{$id}}) {
+           push @$res, $item->{volid} if defined($item->{volid});
+       }
+    }
+
+    return $res;
+}
+
+my $complete_ctid_full = sub {
+    my ($running) = @_;
+
+    my $idlist = vmstatus();
+
+    my $active_hash = list_active_containers();
+
+    my $res = [];
+
+    foreach my $id (keys %$idlist) {
+       my $d = $idlist->{$id};
+       if (defined($running)) {
+           next if $d->{template};
+           next if $running && !$active_hash->{$id};
+           next if !$running && $active_hash->{$id};
+       }
+       push @$res, $id;
+
+    }
+    return $res;
+};
 
-    if(!$err || ($err && $force)) {
-       lock_container($vmid, 10, $del_snap);
-       if ($err) {
-           die "Can't delete snapshot: $vmid $snapname $err\n";
+sub complete_ctid {
+    return &$complete_ctid_full();
+}
+
+sub complete_ctid_stopped {
+    return &$complete_ctid_full(0);
+}
+
+sub complete_ctid_running {
+    return &$complete_ctid_full(1);
+}
+
+sub parse_id_maps {
+    my ($conf) = @_;
+
+    my $id_map = [];
+    my $rootuid = 0;
+    my $rootgid = 0;
+
+    my $lxc = $conf->{lxc};
+    foreach my $entry (@$lxc) {
+       my ($key, $value) = @$entry;
+       # FIXME: remove the 'id_map' variant when lxc-3.0 arrives
+       next if $key ne 'lxc.idmap' && $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 idmap: $value\n";
        }
     }
+
+    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 [];
+}
+
+sub vm_start {
+    my ($vmid, $conf, $skiplock) = @_;
+
+    update_lxc_config($vmid, $conf);
+
+    my $skiplock_flag_fn = "/run/lxc/skiplock-$vmid";
+
+    if ($skiplock) {
+       open(my $fh, '>', $skiplock_flag_fn) || die "failed to open $skiplock_flag_fn for writing: $!\n";
+       close($fh);
+    }
+
+    my $cmd = ['systemctl', 'start', "pve-container\@$vmid"];
+
+    eval { PVE::Tools::run_command($cmd); };
+    if (my $err = $@) {
+       unlink $skiplock_flag_fn;
+       die $err;
+    }
+
+    return;
+}
+
+# Helper to stop a container completely and make sure it has stopped completely.
+# This is necessary because we want the post-stop hook to have completed its
+# unmount-all step, but post-stop happens after lxc puts the container into the
+# STOPPED state.
+sub vm_stop {
+    my ($vmid, $kill, $shutdown_timeout, $exit_timeout) = @_;
+
+    # Open the container's command socket.
+    my $path = "\0/var/lib/lxc/$vmid/command";
+    my $sock = IO::Socket::UNIX->new(
+       Type => SOCK_STREAM(),
+       Peer => $path,
+    );
+    if (!$sock) {
+       return if $! == ECONNREFUSED; # The container is not running
+       die "failed to open container ${vmid}'s command socket: $!\n";
+    }
+
+    # Stop the container:
+
+    my $cmd = ['lxc-stop', '-n', $vmid];
+
+    if ($kill) {
+       push @$cmd, '--kill'; # doesn't allow timeouts
+    } elsif (defined($shutdown_timeout)) {
+       push @$cmd, '--timeout', $shutdown_timeout;
+       # Give run_command 5 extra seconds
+       $shutdown_timeout += 5;
+    }
+
+    eval { PVE::Tools::run_command($cmd, timeout => $shutdown_timeout) };
+    if (my $err = $@) {
+       warn $@ if $@;
+    }
+
+    my $result = 1;
+    my $wait = sub { $result = <$sock>; };
+    if (defined($exit_timeout)) {
+       PVE::Tools::run_with_timeout($exit_timeout, $wait);
+    } else {
+       $wait->();
+    }
+
+    return if !defined $result; # monitor is gone and the ct has stopped.
+    die "container did not stop\n";
+}
+
+sub run_unshared {
+    my ($code) = @_;
+
+    return PVE::Tools::run_fork(sub {
+       # Unshare the mount namespace
+       die "failed to unshare mount namespace: $!\n"
+           if !PVE::Tools::unshare(PVE::Tools::CLONE_NEWNS);
+       PVE::Tools::run_command(['mount', '--make-rslave', '/']);
+       return $code->();
+    });
 }
 
-sub snapshot_rollback {
-    my ($vmid, $snapname) = @_;
+my $copy_volume = sub {
+    my ($src_volid, $src, $dst_volid, $dest, $storage_cfg, $snapname) = @_;
+
+    my $src_mp = { volume => $src_volid, mp => '/' };
+    $src_mp->{type} = PVE::LXC::Config->classify_mountpoint($src_volid);
+
+    my $dst_mp = { volume => $dst_volid, mp => '/' };
+    $dst_mp->{type} = PVE::LXC::Config->classify_mountpoint($dst_volid);
+
+    my @mounted;
+    eval {
+       # mount and copy
+       mkdir $src;
+       mountpoint_mount($src_mp, $src, $storage_cfg, $snapname);
+       push @mounted, $src;
+       mkdir $dest;
+       mountpoint_mount($dst_mp, $dest, $storage_cfg);
+       push @mounted, $dest;
+
+       PVE::Tools::run_command(['/usr/bin/rsync', '--stats', '-X', '-A', '--numeric-ids',
+                                '-aH', '--whole-file', '--sparse', '--one-file-system',
+                                "$src/", $dest]);
+    };
+    my $err = $@;
+    foreach my $mount (reverse @mounted) {
+       eval { PVE::Tools::run_command(['/bin/umount', '--lazy', $mount], errfunc => sub{})};
+       warn "Can't umount $mount\n" if $@;
+    }
+
+    # If this fails they're used as mount points in a concurrent operation
+    # (which should not happen but there's also no real need to get rid of them).
+    rmdir $dest;
+    rmdir $src;
+
+    die $err if $err;
+};
+
+# Should not be called after unsharing the mount namespace!
+sub copy_volume {
+    my ($mp, $vmid, $storage, $storage_cfg, $conf, $snapname) = @_;
+
+    die "cannot copy volumes of type $mp->{type}\n" if $mp->{type} ne 'volume';
+    File::Path::make_path("/var/lib/lxc/$vmid");
+    my $dest = "/var/lib/lxc/$vmid/.copy-volume-1";
+    my $src  = "/var/lib/lxc/$vmid/.copy-volume-2";
 
-    die "Not implemented\n";
+    # get id's for unprivileged container
+    my (undef, $rootuid, $rootgid) = parse_id_maps($conf);
+
+    # Allocate the disk before unsharing in order to make sure zfs subvolumes
+    # are visible in this namespace, otherwise the host only sees the empty
+    # (not-mounted) directory.
+    my $new_volid;
+    eval {
+       # Make sure $mp contains a correct size.
+       $mp->{size} = PVE::Storage::volume_size_info($storage_cfg, $mp->{volume});
+       my $needs_chown;
+       ($new_volid, $needs_chown) = alloc_disk($storage_cfg, $vmid, $storage, $mp->{size}/1024, $rootuid, $rootgid);
+       if ($needs_chown) {
+           PVE::Storage::activate_volumes($storage_cfg, [$new_volid], undef);
+           my $path = PVE::Storage::path($storage_cfg, $new_volid, undef);
+           chown($rootuid, $rootgid, $path);
+       }
+
+       run_unshared(sub {
+           $copy_volume->($mp->{volume}, $src, $new_volid, $dest, $storage_cfg, $snapname);
+       });
+    };
+    if (my $err = $@) {
+       PVE::Storage::vdisk_free($storage_cfg, $new_volid)
+           if defined($new_volid);
+       die $err;
+    }
+
+    return $new_volid;
 }
+
 1;