]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
lxc: read-only bind mounts
[pve-container.git] / src / PVE / LXC.pm
index 3c77c5bc0b9053a067e751ef92babd97591bc275..a737fc0d3ab4195f4aa2d05053e9eddd0135de43 100644 (file)
@@ -4,52 +4,81 @@ use strict;
 use warnings;
 use POSIX qw(EINTR);
 
+use Socket;
+
 use File::Path;
 use File::Spec;
 use Cwd qw();
-use Fcntl ':flock';
+use Fcntl qw(O_RDONLY);
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file);
 use PVE::Storage;
 use PVE::SafeSyslog;
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option);
-use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
+use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
 use PVE::Network;
 use PVE::AccessControl;
 use PVE::ProcFSTools;
+use Time::HiRes qw (gettimeofday);
 
 use Data::Dumper;
 
 my $nodename = PVE::INotify::nodename();
 
-cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
-
-PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
-sub verify_lxc_network {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_lxc_network($value);
-
-    return undef if $noerr;
-
-    die "unable to parse network setting\n";
-}
-
-PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
-sub verify_ct_mountpoint {
-    my ($value, $noerr) = @_;
+my $cpuinfo= PVE::ProcFSTools::read_cpuinfo();
 
-    return $value if parse_ct_mountpoint($value);
+our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
+                          '--xattrs',
+                          '--xattrs-include=user.*',
+                          '--xattrs-include=security.capability',
+                          '--warning=no-xattr-write' ];
 
-    return undef if $noerr;
+cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
 
-    die "unable to parse CT mountpoint options\n";
-}
+my $rootfs_desc = {
+    volume => {
+       type => 'string',
+       default_key => 1,
+       format => 'pve-lxc-mp-string',
+       format_description => 'volume',
+       description => 'Volume, device or directory to mount into the container.',
+    },
+    backup => {
+       type => 'boolean',
+       format_description => '[1|0]',
+       description => 'Whether to include the mountpoint in backups.',
+       optional => 1,
+    },
+    size => {
+       type => 'string',
+       format => 'disk-size',
+       format_description => 'DiskSize',
+       description => 'Volume size (read only value).',
+       optional => 1,
+    },
+    acl => {
+       type => 'boolean',
+       format_description => 'acl',
+       description => 'Explicitly enable or disable ACL support.',
+       optional => 1,
+    },
+    ro => {
+       type => 'boolean',
+       format_description => 'ro',
+       description => 'Read-only mountpoint (not supported with bind mounts)',
+       optional => 1,
+    },
+    quota => {
+       type => 'boolean',
+       format_description => '[0|1]',
+       description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
+       optional => 1,
+    },
+};
 
 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
-    type => 'string', format => 'pve-ct-mountpoint',
-    typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
+    type => 'string', format => $rootfs_desc,
     description => "Use volume as container root.",
     optional => 1,
 });
@@ -90,7 +119,7 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-       enum => ['debian', 'ubuntu', 'centos', 'archlinux'],
+       enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'],
        description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
     },
     console => {
@@ -110,7 +139,7 @@ my $confdesc = {
     cpulimit => {
        optional => 1,
        type => 'number',
-       description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
+       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,
@@ -118,7 +147,7 @@ my $confdesc = {
     cpuunits => {
        optional => 1,
        type => 'integer',
-       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
        minimum => 0,
        maximum => 500000,
        default => 1024,
@@ -151,12 +180,12 @@ my $confdesc = {
     searchdomain => {
        optional => 1,
        type => 'string', format => 'dns-name-list',
-       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
+       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
     },
     nameserver => {
        optional => 1,
        type => 'string', format => 'address-list',
-       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
+       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
     },
     rootfs => get_standard_option('pve-ct-rootfs'),
     parent => {
@@ -181,7 +210,13 @@ my $confdesc = {
     protection => {
        optional => 1,
        type => 'boolean',
-       description => "Sets the protection flag of the container. This will prevent the remove operation.",
+       description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
+       default => 0,
+    },
+    unprivileged => {
+       optional => 1,
+       type => 'boolean',
+       description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
        default => 0,
     },
 };
@@ -217,9 +252,10 @@ my $valid_lxc_conf_keys = {
     'lxc.mount' => 1,
     'lxc.mount.entry' => 1,
     'lxc.mount.auto' => 1,
-    'lxc.rootfs' => 1,
+    'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
     'lxc.rootfs.mount' => 1,
-    'lxc.rootfs.options' => 1,
+    'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
+                            ', please use mountpoint options in the "rootfs" key',
     # lxc.cgroup.*
     'lxc.cap.drop' => 1,
     'lxc.cap.keep' => 1,
@@ -232,6 +268,7 @@ my $valid_lxc_conf_keys = {
     'lxc.hook.pre-mount' => 1,
     'lxc.hook.mount' => 1,
     'lxc.hook.start' => 1,
+    'lxc.hook.stop' => 1,
     'lxc.hook.post-stop' => 1,
     'lxc.hook.clone' => 1,
     'lxc.hook.destroy' => 1,
@@ -242,37 +279,153 @@ my $valid_lxc_conf_keys = {
     'lxc.start.order' => 1,
     'lxc.group' => 1,
     'lxc.environment' => 1,
-    'lxc.' => 1,
-    'lxc.' => 1,
-    'lxc.' => 1,
-    'lxc.' => 1,
 };
 
+my $netconf_desc = {
+    type => {
+       type => 'string',
+       optional => 1,
+       description => "Network interface type.",
+       enum => [qw(veth)],
+    },
+    name => {
+       type => 'string',
+       format_description => 'String',
+       description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
+       pattern => '[-_.\w\d]+',
+    },
+    bridge => {
+       type => 'string',
+       format_description => 'vmbr<Number>',
+       description => 'Bridge to attach the network device to.',
+       pattern => '[-_.\w\d]+',
+       optional => 1,
+    },
+    hwaddr => {
+       type => 'string',
+       format_description => 'MAC',
+       description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
+       pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
+       optional => 1,
+    },
+    mtu => {
+       type => 'integer',
+       format_description => 'Number',
+       description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
+       minimum => 64, # minimum ethernet frame is 64 bytes
+       optional => 1,
+    },
+    ip => {
+       type => 'string',
+       format => 'pve-ipv4-config',
+       format_description => 'IPv4Format/CIDR',
+       description => 'IPv4 address in CIDR format.',
+       optional => 1,
+    },
+    gw => {
+       type => 'string',
+       format => 'ipv4',
+       format_description => 'GatewayIPv4',
+       description => 'Default gateway for IPv4 traffic.',
+       optional => 1,
+    },
+    ip6 => {
+       type => 'string',
+       format => 'pve-ipv6-config',
+       format_description => 'IPv6Format/CIDR',
+       description => 'IPv6 address in CIDR format.',
+       optional => 1,
+    },
+    gw6 => {
+       type => 'string',
+       format => 'ipv6',
+       format_description => 'GatewayIPv6',
+       description => 'Default gateway for IPv6 traffic.',
+       optional => 1,
+    },
+    firewall => {
+       type => 'boolean',
+       format_description => '[1|0]',
+       description => "Controls whether this interface's firewall rules should be used.",
+       optional => 1,
+    },
+    tag => {
+       type => 'integer',
+       format_description => 'VlanNo',
+       minimum => '2',
+       maximum => '4094',
+       description => "VLAN tag for this interface.",
+       optional => 1,
+    },
+    trunks => {
+       type => 'string',
+       pattern => qr/\d+(?:;\d+)*/,
+       format_description => 'vlanid[;vlanid...]',
+       description => "VLAN ids to pass through the interface",
+       optional => 1,
+    },
+};
+PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
+
 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>]",
+       type => 'string', format => $netconf_desc,
+       description => "Specifies network interfaces for the container.",
     };
 }
 
+PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
+sub verify_lxc_mp_string{
+    my ($mp, $noerr) = @_;
+
+    # do not allow:
+    # /./ or /../ 
+    # /. or /.. at the end
+    # ../ at the beginning
+    
+    if($mp =~ m@/\.\.?/@ ||
+       $mp =~ m@/\.\.?$@ ||
+       $mp =~ m@^\.\./@){
+       return undef if $noerr;
+       die "$mp contains illegal character sequences\n";
+    }
+    return $mp;
+}
+
+my $mp_desc = {
+    %$rootfs_desc,
+    mp => {
+       type => 'string',
+       format => 'pve-lxc-mp-string',
+       format_description => 'Path',
+       description => 'Path to the mountpoint as seen from inside the container.',
+    },
+};
+PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
+
+my $unuseddesc = {
+    optional => 1,
+    type => 'string', format => 'pve-volume-id',
+    description => "Reference to unused volumes.",
+};
+
 my $MAX_MOUNT_POINTS = 10;
 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
     $confdesc->{"mp$i"} = {
        optional => 1,
-       type => 'string', format => 'pve-ct-mountpoint',
-       typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
+       type => 'string', format => $mp_desc,
        description => "Use volume as container mount point (experimental feature).",
        optional => 1,
     };
 }
 
+my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"unused$i"} = $unuseddesc;
+}
+
 sub write_pct_config {
     my ($filename, $conf) = @_;
 
@@ -392,8 +545,11 @@ sub parse_pct_config {
        if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
            my $key = $1;
            my $value = $3;
-           if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
+           my $validity = $valid_lxc_conf_keys->{$key} || 0;
+           if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
                push @{$conf->{lxc}}, [$key, $value];
+           } elsif (my $errmsg = $validity) {
+               warn "vm $vmid - $key: $errmsg\n";
            } else {
                warn "vm $vmid - unable to parse config: $line\n";
            }
@@ -456,7 +612,7 @@ sub load_config {
     my $cfspath = cfs_config_path($vmid, $node);
 
     my $conf = PVE::Cluster::cfs_read_file($cfspath);
-    die "container $vmid does not exists\n" if !defined($conf);
+    die "container $vmid does not exist\n" if !defined($conf);
 
     return $conf;
 }
@@ -485,89 +641,49 @@ sub write_config {
 }
 
 # flock: we use one file handle per process, so lock file
-# can be called multiple times and succeeds for the same process.
+# can be called multiple times and will succeed for the same process.
 
 my $lock_handles =  {};
 my $lockdir = "/run/lock/lxc";
 
-sub lock_filename {
+sub config_file_lock {
     my ($vmid) = @_;
 
     return "$lockdir/pve-config-${vmid}.lock";
 }
 
-sub lock_aquire {
-    my ($vmid, $timeout) = @_;
-
-    $timeout = 10 if !$timeout;
-    my $mode = LOCK_EX;
+sub lock_config_full {
+    my ($vmid, $timeout, $code, @param) = @_;
 
-    my $filename = lock_filename($vmid);
+    my $filename = config_file_lock($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};
-       }
-
-       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";
-           }
+    my $res = lock_file($filename, $timeout, $code, @param);
 
-           print STDERR " OK\n";
-       }
-       
-       $lock_handles->{$$}->{$filename}->{refcount}++;
-    };
+    die $@ if $@;
 
-    eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
-    my $err = $@;
-    if ($err) {
-       die "can't lock file '$filename' - $err";
-    }
+    return $res;
 }
 
-sub lock_release {
-    my ($vmid) = @_;
+sub lock_config_mode {
+    my ($vmid, $timeout, $shared, $code, @param) = @_;
 
-    my $filename = lock_filename($vmid);
+    my $filename = config_file_lock($vmid);
 
-    if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
-       my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
-       if ($refcount <= 0) {
-           $lock_handles->{$$}->{$filename} = undef;
-           close ($fh);
-       }
-    }
-}
+    mkdir $lockdir if !-d $lockdir;
 
-sub lock_container {
-    my ($vmid, $timeout, $code, @param) = @_;
+    my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
 
-    my $res;
+    die $@ if $@;
 
-    lock_aquire($vmid, $timeout);
-    eval { $res = &$code(@param) };
-    my $err = $@;
-    lock_release($vmid);
+    return $res;
+}
 
-    die $err if $err;
+sub lock_config {
+    my ($vmid, $code, @param) = @_;
 
-    return $res;
+    return lock_config_full($vmid, 10, $code, @param);
 }
 
 sub option_exists {
@@ -589,18 +705,6 @@ sub json_config_properties {
     return $prop;
 }
 
-sub json_config_properties_no_rootfs {
-    my $prop = shift;
-
-    foreach my $opt (keys %$confdesc) {
-       next if $prop->{$opt};
-       next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
-       $prop->{$opt} = $confdesc->{$opt};
-    }
-
-    return $prop;
-}
-
 # container status helpers
 
 sub list_active_containers {
@@ -639,32 +743,29 @@ sub check_running {
 }
 
 sub get_container_disk_usage {
+    my ($vmid, $pid) = @_;
+
+    return PVE::Tools::df("/proc/$pid/root/", 1);
+}
+
+my $last_proc_vmid_stat;
+
+my $parse_cpuacct_stat = sub {
     my ($vmid) = @_;
 
-    my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df',  '-P', '-B', '1', '/'];
+    my $raw = read_cgroup_value('cpuacct', $vmid, 'cpuacct.stat', 1);
 
-    my $res = {
-       total => 0,
-       used => 0,
-       avail => 0,
-    };
+    my $stat = {};
 
-    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 $@;
+    if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
 
-    return $res;
-}
+       $stat->{utime} = $1;
+       $stat->{stime} = $2;
+
+    }
+
+    return $stat;
+};
 
 sub vmstatus {
     my ($opt_vmid) = @_;
@@ -673,12 +774,19 @@ sub vmstatus {
 
     my $active_hash = list_active_containers();
 
+    my $cpucount = $cpuinfo->{cpus} || 1;
+
+    my $cdtime = gettimeofday;
+
+    my $uptime = (PVE::ProcFSTools::read_proc_uptime(1))[0];
+
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
 
-       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 $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
@@ -686,17 +794,17 @@ sub vmstatus {
        $d->{name} = $conf->{'hostname'} || "CT$vmid";
        $d->{name} =~ s/[\s]//g;
 
-       $d->{cpus} = $conf->{cpulimit} // 0;
+       $d->{cpus} = $conf->{cpulimit} || $cpucount;
 
-       if ($running) {
-           my $res = get_container_disk_usage($vmid);
+       if ($d->{pid}) {
+           my $res = get_container_disk_usage($vmid, $d->{pid});
            $d->{disk} = $res->{used};
            $d->{maxdisk} = $res->{total};
        } else {
            $d->{disk} = 0;
            # use 4GB by default ??
            if (my $rootfs = $conf->{rootfs}) {
-               my $rootinfo = parse_ct_mountpoint($rootfs);
+               my $rootinfo = parse_ct_rootfs($rootfs);
                $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
            } else {
                $d->{maxdisk} = 4*1024*1024*1024;
@@ -722,9 +830,12 @@ sub vmstatus {
 
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
-       next if $d->{status} ne 'running';
+       my $pid = $d->{pid};
+
+       next if !$pid; # skip stopped CTs
 
-       $d->{uptime} = 100; # fixme:
+       my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
+       $d->{uptime} = time - $ctime; # the method lxcfs uses
 
        $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
        $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
@@ -737,112 +848,115 @@ sub vmstatus {
                $d->{diskwrite} = $2 if $key eq 'Write';
            }
        }
-    }
 
-    return $list;
-}
+       my $pstat = &$parse_cpuacct_stat($vmid);
 
-my $parse_size = sub {
-    my ($value) = @_;
+       my $used = $pstat->{utime} + $pstat->{stime};
 
-    return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
-    my ($size, $unit) = ($1, $3);
-    if ($unit) {
-       if ($unit eq 'K') {
-           $size = $size * 1024;
-       } elsif ($unit eq 'M') {
-           $size = $size * 1024 * 1024;
-       } elsif ($unit eq 'G') {
-           $size = $size * 1024 * 1024 * 1024;
+       my $old = $last_proc_vmid_stat->{$vmid};
+       if (!$old) {
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => 0,
+           };
+           next;
        }
-    }
-    return int($size);
-};
-
-my $format_size = sub {
-    my ($size) = @_;
 
-    $size = int($size);
+       my $dtime = ($cdtime -  $old->{time}) * $cpucount * $cpuinfo->{user_hz};
 
-    my $kb = int($size/1024);
-    return $size if $kb*1024 != $size;
+       if ($dtime > 1000) {
+           my $dutime = $used -  $old->{used};
 
-    my $mb = int($kb/1024);
-    return "${kb}K" if $mb*1024 != $kb;
+           $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus};
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => $d->{cpu},
+           };
+       } else {
+           $d->{cpu} = $old->{cpu};
+       }
+    }
 
-    my $gb = int($mb/1024);
-    return "${mb}M" if $gb*1024 != $mb;
+    my $netdev = PVE::ProcFSTools::read_proc_net_dev();
 
-    return "${gb}G";
-};
+    foreach my $dev (keys %$netdev) {
+       next if $dev !~ m/^veth([1-9]\d*)i/;
+       my $vmid = $1;
+       my $d = $list->{$vmid};
 
-sub parse_ct_mountpoint {
-    my ($data) = @_;
+       next if !$d;
 
-    $data //= '';
+       $d->{netout} += $netdev->{$dev}->{receive};
+       $d->{netin} += $netdev->{$dev}->{transmit};
 
-    my $res = {};
+    }
 
-    foreach my $p (split (/,/, $data)) {
-       next if $p =~ m/^\s*$/;
+    return $list;
+}
 
-       if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
-           my ($k, $v) = ($1, $2);
-           return undef if defined($res->{$k});
-           $res->{$k} = $v;
-       } else {
-           if (!$res->{volume} && $p !~ m/=/) {
-               $res->{volume} = $p;
-           } else {
-               return undef;
-           }
-       }
+sub classify_mountpoint {
+    my ($vol) = @_;
+    if ($vol =~ m!^/!) {
+       return 'device' if $vol =~ m!^/dev/!;
+       return 'bind';
     }
+    return 'volume';
+}
 
-    return undef if !defined($res->{volume});
+my $parse_ct_mountpoint_full = sub {
+    my ($desc, $data, $noerr) = @_;
 
-    return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
+    $data //= '';
 
-    if ($res->{size}) {
-       return undef if !defined($res->{size} = &$parse_size($res->{size}));
+    my $res;
+    eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
+    if ($@) {
+       return undef if $noerr;
+       die $@;
     }
 
+    if (defined(my $size = $res->{size})) {
+       $size = PVE::JSONSchema::parse_size($size);
+       if (!defined($size)) {
+           return undef if $noerr;
+           die "invalid size: $size\n";
+       }
+       $res->{size} = $size;
+    }
+
+    $res->{type} = classify_mountpoint($res->{volume});
+
     return $res;
-}
+};
 
-sub print_ct_mountpoint {
-    my ($info, $nomp) = @_;
+sub parse_ct_rootfs {
+    my ($data, $noerr) = @_;
 
-    my $opts = '';
+    my $res =  &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
 
-    die "missing volume\n" if !$info->{volume};
+    $res->{mp} = '/' if defined($res);
 
-    foreach my $o (qw(backup)) {
-       $opts .= ",$o=$info->{$o}" if defined($info->{$o});
-    }
+    return $res;
+}
 
-    if ($info->{size}) {
-       $opts .= ",size=" . &$format_size($info->{size});
-    }
+sub parse_ct_mountpoint {
+    my ($data, $noerr) = @_;
 
-    $opts .= ",mp=$info->{mp}" if !$nomp;
+    return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
+}
 
-    return "$info->{volume}$opts";
+sub print_ct_mountpoint {
+    my ($info, $nomp) = @_;
+    my $skip = [ 'type' ];
+    push @$skip, 'mp' if $nomp;
+    return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
 }
 
 sub print_lxc_network {
     my $net = shift;
-
-    die "no network name defined\n" if !$net->{name};
-
-    my $res = "name=$net->{name}";
-
-    foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
-       next if !defined($net->{$k});
-       $res .= ",$k=$net->{$k}";
-    }
-
-    return $res;
+    return PVE::JSONSchema::print_property_string($net, $netconf_desc);
 }
 
 sub parse_lxc_network {
@@ -852,13 +966,7 @@ sub parse_lxc_network {
 
     return $res if !$data;
 
-    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 = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
 
     $res->{type} = 'veth';
     $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
@@ -896,7 +1004,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+$/;
@@ -918,56 +1026,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;
@@ -981,6 +1053,14 @@ sub check_lock {
     die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
 }
 
+sub check_protection {
+    my ($vm_conf, $err_msg) = @_;
+
+    if ($vm_conf->{protection}) {
+       die "$err_msg - protection mode enabled\n";
+    }
+}
+
 sub update_lxc_config {
     my ($storage_cfg, $vmid, $conf) = @_;
 
@@ -998,11 +1078,30 @@ sub update_lxc_config {
     die "missing 'arch' - internal error" if !$conf->{arch};
     $raw .= "lxc.arch = $conf->{arch}\n";
 
+    my $unprivileged = $conf->{unprivileged};
+    my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
+
     my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
-    if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
+    if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
        $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
+       if ($unprivileged || $custom_idmap) {
+           $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
+       }
     } else {
-       die "implement me";
+       die "implement me (ostype $ostype)";
+    }
+
+    # 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";
+
+    # Should we read them from /etc/subuid?
+    if ($unprivileged && !$custom_idmap) {
+       $raw .= "lxc.id_map = u 0 100000 65536\n";
+       $raw .= "lxc.id_map = g 0 100000 65536\n";
     }
 
     if (!has_dev_console($conf)) {
@@ -1013,6 +1112,9 @@ sub update_lxc_config {
     my $ttycount = get_tty_count($conf);
     $raw .= "lxc.tty = $ttycount\n";
 
+    # some init scripts expect a linux terminal (turnkey).
+    $raw .= "lxc.environment = TERM=linux\n";
+    
     my $utsname = $conf->{hostname} || "CT$vmid";
     $raw .= "lxc.utsname = $utsname\n";
 
@@ -1034,13 +1136,9 @@ sub update_lxc_config {
     my $shares = $conf->{cpuunits} || 1024;
     $raw .= "lxc.cgroup.cpu.shares = $shares\n";
 
-    my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
-    $mountpoint->{mp} = '/';
-    
-    my ($path, $use_loopdev) = mountpoint_mount_path($mountpoint, $storage_cfg);
-    $path = "loop:$path" if $use_loopdev;
+    my $mountpoint = parse_ct_rootfs($conf->{rootfs});
 
-    $raw .= "lxc.rootfs = $path\n";
+    $raw .= "lxc.rootfs = $dir/rootfs\n";
 
     my $netcount = 0;
     foreach my $k (keys %$conf) {
@@ -1095,12 +1193,33 @@ sub verify_searchdomain_list {
     return join(' ', @list);
 }
 
+sub add_unused_volume {
+    my ($config, $volid) = @_;
+
+    my $key;
+    for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
+       my $test = "unused$ind";
+       if (my $vid = $config->{$test}) {
+           return if $vid eq $volid; # do not add duplicates
+       } else {
+           $key = $test;
+       }
+    }
+
+    die "Too many unused volumes - please delete them first.\n" if !$key;
+
+    $config->{$key} = $volid;
+
+    return $key;
+}
+
 sub update_pct_config {
     my ($vmid, $conf, $running, $param, $delete) = @_;
 
     my @nohotplug;
 
     my $new_disks = 0;
+    my @deleted_volumes;
 
     my $rootdir;
     if ($running) {
@@ -1108,8 +1227,22 @@ sub update_pct_config {
        $rootdir = "/proc/$pid/root";
     }
 
+    my $hotplug_error = sub {
+       if ($running) {
+           push @nohotplug, @_;
+           return 1;
+       } else {
+           return 0;
+       }
+    };
+
     if (defined($delete)) {
        foreach my $opt (@$delete) {
+           if (!exists($conf->{$opt})) {
+               warn "no such option: $opt\n";
+               next;
+           }
+
            if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
                die "unable to delete required option '$opt'\n";
            } elsif ($opt eq 'swap') {
@@ -1119,9 +1252,8 @@ sub update_pct_config {
                delete $conf->{$opt};
            } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
                     $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+               next if $hotplug_error->($opt);
                delete $conf->{$opt};
-               push @nohotplug, $opt;
-               next if $running;
            } elsif ($opt =~ m/^net(\d)$/) {
                delete $conf->{$opt};
                next if !$running;
@@ -1129,14 +1261,23 @@ sub update_pct_config {
                PVE::Network::veth_delete("veth${vmid}i$netid");
            } elsif ($opt eq 'protection') {
                delete $conf->{$opt};
+           } elsif ($opt =~ m/^unused(\d+)$/) {
+               next if $hotplug_error->($opt);
+               check_protection($conf, "can't remove CT $vmid drive '$opt'");
+               push @deleted_volumes, $conf->{$opt};
+               delete $conf->{$opt};
            } elsif ($opt =~ m/^mp(\d+)$/) {
+               next if $hotplug_error->($opt);
+               check_protection($conf, "can't remove CT $vmid drive '$opt'");
+               my $mountpoint = parse_ct_mountpoint($conf->{$opt});
+               if ($mountpoint->{type} eq 'volume') {
+                   add_unused_volume($conf, $mountpoint->{volume})
+               }
                delete $conf->{$opt};
-               push @nohotplug, $opt;
-               next if $running;
-           } elsif ($opt eq 'rootfs') {
-               die "implement me"
+           } elsif ($opt eq 'unprivileged') {
+               die "unable to delete read-only option: '$opt'\n";
            } else {
-               die "implement me"
+               die "implement me (delete: $opt)"
            }
            write_config($vmid, $conf) if $running;
        }
@@ -1148,13 +1289,22 @@ sub update_pct_config {
     my $wanted_swap =  PVE::Tools::extract_param($param, 'swap');
     if (defined($wanted_memory) || defined($wanted_swap)) {
 
-       $wanted_memory //= ($conf->{memory} || 512);
-       $wanted_swap //=  ($conf->{swap} || 0);
+       my $old_memory = ($conf->{memory} || 512);
+       my $old_swap = ($conf->{swap} || 0);
+
+       $wanted_memory //= $old_memory;
+       $wanted_swap //= $old_swap;
 
         my $total = $wanted_memory + $wanted_swap;
        if ($running) {
-           write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
-           write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
+           my $old_total = $old_memory + $old_swap;
+           if ($total > $old_total) {
+               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
+               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
+           } else {
+               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
+               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
+           }
        }
        $conf->{memory} = $wanted_memory;
        $conf->{swap} = $wanted_swap;
@@ -1171,23 +1321,19 @@ sub update_pct_config {
        } elsif ($opt eq 'startup') {
            $conf->{$opt} = $value;
        } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+           next if $hotplug_error->($opt);
            $conf->{$opt} = $value;
-           push @nohotplug, $opt;
-           next if $running;
        } elsif ($opt eq 'nameserver') {
+           next if $hotplug_error->($opt);
            my $list = verify_nameserver_list($value);
            $conf->{$opt} = $list;
-           push @nohotplug, $opt;
-           next if $running;
        } elsif ($opt eq 'searchdomain') {
+           next if $hotplug_error->($opt);
            my $list = verify_searchdomain_list($value);
            $conf->{$opt} = $list;
-           push @nohotplug, $opt;
-           next if $running;
        } elsif ($opt eq 'cpulimit') {
+           next if $hotplug_error->($opt); # FIXME: hotplug
            $conf->{$opt} = $value;
-           push @nohotplug, $opt; # fixme: hotplug
-           next;
        } elsif ($opt eq 'cpuunits') {
            $conf->{$opt} = $value;
            write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
@@ -1204,26 +1350,38 @@ sub update_pct_config {
        } elsif ($opt eq 'protection') {
            $conf->{$opt} = $value ? 1 : 0;
         } elsif ($opt =~ m/^mp(\d+)$/) {
+           next if $hotplug_error->($opt);
+           check_protection($conf, "can't update CT $vmid drive '$opt'");
            $conf->{$opt} = $value;
            $new_disks = 1;
-           push @nohotplug, $opt;
-           next;
         } elsif ($opt eq 'rootfs') {
-           die "implement me: $opt";
+           next if $hotplug_error->($opt);
+           check_protection($conf, "can't update CT $vmid drive '$opt'");
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'unprivileged') {
+           die "unable to modify read-only option: '$opt'\n";
        } else {
            die "implement me: $opt";
        }
        write_config($vmid, $conf) if $running;
     }
 
-    if ($running && scalar(@nohotplug)) {
-       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
+    if (@deleted_volumes) {
+       my $storage_cfg = PVE::Storage::config();
+       foreach my $volume (@deleted_volumes) {
+           delete_mountpoint_volume($storage_cfg, $vmid, $volume);
+       }
     }
 
     if ($new_disks) {
        my $storage_cfg = PVE::Storage::config();
        create_disks($storage_cfg, $vmid, $conf, $conf);
     }
+
+    # This should be the last thing we do here
+    if ($running && scalar(@nohotplug)) {
+       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
+    }
 }
 
 sub has_dev_console {
@@ -1278,7 +1436,7 @@ sub get_primary_ips {
     }
     my $ipv6 = $net->{ip6};
     if ($ipv6) {
-       if ($ipv6 =~ /^(dhcp|manual)$/) {
+       if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
            $ipv6 = undef;
        } else {
            $ipv6 =~ s!/\d+$!!;
@@ -1288,14 +1446,21 @@ sub get_primary_ips {
     return ($ipv4, $ipv6);
 }
 
+sub delete_mountpoint_volume {
+    my ($storage_cfg, $vmid, $volume) = @_;
+
+    return if classify_mountpoint($volume) ne 'volume';
+
+    my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
+    PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
+}
 
 sub destroy_lxc_container {
     my ($storage_cfg, $vmid, $conf) = @_;
 
     foreach_mountpoint($conf, sub {
        my ($ms, $mountpoint) = @_;
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
-       PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
+       delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
     });
 
     rmdir "/var/lib/lxc/$vmid/rootfs";
@@ -1376,7 +1541,7 @@ sub update_net {
                    write_config($vmid, $conf);
                }
 
-               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
                foreach (qw(bridge tag firewall)) {
                    $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
                }
@@ -1398,7 +1563,7 @@ sub hotplug_net {
     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});
 
     # attach peer in container
     my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
@@ -1449,7 +1614,8 @@ sub update_ipconfig {
        return if !$change_ip && !$change_gw;
 
        # step 1: add new IP, if this fails we cancel
-       if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
+       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;
@@ -1464,7 +1630,12 @@ sub update_ipconfig {
        # Note: 'ip route replace' can add
        if ($change_gw) {
            if ($newgw) {
-               eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $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
@@ -1524,8 +1695,8 @@ sub update_ipconfig {
 # Internal snapshots
 
 # NOTE: Snapshot create/delete involves several non-atomic
-# action, and can take a long time.
-# So we try to avoid locking the file and use 'lock' variable
+# actions, and can take a long time.
+# So we try to avoid locking the file and use the 'lock' variable
 # inside the config file instead.
 
 my $snapshot_copy_config = sub {
@@ -1564,7 +1735,8 @@ my $snapshot_prepare = sub {
            if defined($conf->{snapshots}->{$snapname});
 
        my $storecfg = PVE::Storage::config();
-       die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
+       my $feature = $snapname eq 'vzdump' ? 'vzdump' : 'snapshot';
+       die "snapshot feature is not available\n" if !has_feature($feature, $conf, $storecfg);
 
        $snap = $conf->{snapshots}->{$snapname} = {};
 
@@ -1578,7 +1750,7 @@ my $snapshot_prepare = sub {
        write_config($vmid, $conf);
     };
 
-    lock_container($vmid, 10, $updatefn);
+    lock_config($vmid, $updatefn);
 
     return $snap;
 };
@@ -1607,18 +1779,21 @@ my $snapshot_commit = sub {
        write_config($vmid, $conf);
     };
 
-    lock_container($vmid, 10 ,$updatefn);
+    lock_config($vmid ,$updatefn);
 };
 
 sub has_feature {
     my ($feature, $conf, $storecfg, $snapname) = @_;
     
     my $err;
+    my $vzdump = $feature eq 'vzdump';
+    $feature = 'snapshot' if $vzdump;
 
     foreach_mountpoint($conf, sub {
        my ($ms, $mountpoint) = @_;
 
        return if $err; # skip further test
+       return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup};
        
        $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
 
@@ -1630,6 +1805,75 @@ sub has_feature {
     return $err ? 0 : 1;
 }
 
+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) = @_;
+
+    &$enter_namespace($vmid, $pid, 'mnt', PVE::Tools::CLONE_NEWNS);
+
+    # Tell the parent process to start reading our /proc/mounts
+    print {$socket} "go\n";
+    $socket->flush();
+
+    # Receive /proc/self/mounts
+    my $mountdata = do { local $/ = undef; <$socket> };
+    close $socket;
+
+    # 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 $@;
+    }
+};
+
+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;
+}
+
 sub snapshot_create {
     my ($vmid, $snapname, $comment) = @_;
 
@@ -1637,33 +1881,45 @@ sub snapshot_create {
 
     my $conf = load_config($vmid);
 
-    my $cmd = "/usr/bin/lxc-freeze -n $vmid";
     my $running = check_running($vmid);
+    
+    my $unfreeze = 0;
+
+    my $drivehash = {};
+
     eval {
        if ($running) {
-           PVE::Tools::run_command($cmd);
+           $unfreeze = 1;
+           PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
+           sync_container_namespace($vmid);
        };
 
        my $storecfg = PVE::Storage::config();
-       my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
+       my $rootinfo = parse_ct_rootfs($conf->{rootfs});
        my $volid = $rootinfo->{volume};
 
-       $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
-       if ($running) {
-           PVE::Tools::run_command($cmd);
-       };
-
        PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
-       &$snapshot_commit($vmid, $snapname);
+       $drivehash->{rootfs} = 1;
     };
-    if(my $err = $@) {
-       snapshot_delete($vmid, $snapname, 1);
+    my $err = $@;
+    
+    if ($unfreeze) {
+       eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
+       warn $@ if $@;
+    }
+    
+    if ($err) {
+       eval { snapshot_delete($vmid, $snapname, 1, $drivehash); };
+       warn "$@\n" if $@;
        die "$err\n";
     }
+
+    &$snapshot_commit($vmid, $snapname);
 }
 
+# Note: $drivehash is only set when called from snapshot_create.
 sub snapshot_delete {
-    my ($vmid, $snapname, $force) = @_;
+    my ($vmid, $snapname, $force, $drivehash) = @_;
 
     my $snap;
 
@@ -1678,7 +1934,9 @@ sub snapshot_delete {
 
        $snap = $conf->{snapshots}->{$snapname};
 
-       check_lock($conf);
+       if (!$drivehash) {
+           check_lock($conf);
+       }
 
        die "snapshot '$snapname' does not exist\n" if !defined($snap);
 
@@ -1687,21 +1945,39 @@ sub snapshot_delete {
        write_config($vmid, $conf);
     };
 
-    lock_container($vmid, 10, $updatefn);
+    lock_config($vmid, $updatefn);
 
     my $storecfg = PVE::Storage::config();
 
-    my $del_snap =  sub {
+    my $unlink_parent = sub {
 
-       check_lock($conf);
+       my ($confref, $new_parent) = @_;
 
-       if ($conf->{parent} eq $snapname) {
-           if ($conf->{snapshots}->{$snapname}->{snapname}) {
-               $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
+       if ($confref->{parent} && $confref->{parent} eq $snapname) {
+           if ($new_parent) {
+               $confref->{parent} = $new_parent;
            } else {
-               delete $conf->{parent};
+               delete $confref->{parent};
            }
        }
+    };
+
+    my $del_snap =  sub {
+
+       $conf = load_config($vmid);
+
+       if ($drivehash) {
+           delete $conf->{lock};
+       } else {
+           check_lock($conf);
+       }
+
+       my $parent = $conf->{snapshots}->{$snapname}->{parent};
+       foreach my $snapkey (keys %{$conf->{snapshots}}) {
+           &$unlink_parent($conf->{snapshots}->{$snapkey}, $parent);
+       }
+
+       &$unlink_parent($conf, $parent);
 
        delete $conf->{snapshots}->{$snapname};
 
@@ -1709,7 +1985,7 @@ sub snapshot_delete {
     };
 
     my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
-    my $rootinfo = parse_ct_mountpoint($rootfs);
+    my $rootinfo = parse_ct_rootfs($rootfs);
     my $volid = $rootinfo->{volume};
 
     eval {
@@ -1718,7 +1994,7 @@ sub snapshot_delete {
     my $err = $@;
 
     if(!$err || ($err && $force)) {
-       lock_container($vmid, 10, $del_snap);
+       lock_config($vmid, $del_snap);
        if ($err) {
            die "Can't delete snapshot: $vmid $snapname $err\n";
        }
@@ -1739,7 +2015,7 @@ sub snapshot_rollback {
     die "snapshot '$snapname' does not exist\n" if !defined($snap);
 
     my $rootfs = $snap->{rootfs};
-    my $rootinfo = parse_ct_mountpoint($rootfs);
+    my $rootinfo = parse_ct_rootfs($rootfs);
     my $volid = $rootinfo->{volume};
 
     PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
@@ -1777,11 +2053,11 @@ sub snapshot_rollback {
        write_config($vmid, $conf);
     };
 
-    lock_container($vmid, 10, $updatefn);
+    lock_config($vmid, $updatefn);
 
     PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
 
-    lock_container($vmid, 5, $unlockfn);
+    lock_config($vmid, $unlockfn);
 }
 
 sub template_create {
@@ -1789,7 +2065,7 @@ sub template_create {
 
     my $storecfg = PVE::Storage::config();
 
-    my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
+    my $rootinfo = parse_ct_rootfs($conf->{rootfs});
     my $volid = $rootinfo->{volume};
 
     die "Template feature is not available for '$volid'\n"
@@ -1822,18 +2098,6 @@ sub mountpoint_names {
     return $reverse ? reverse @names : @names;
 }
 
-# The container might have *different* symlinks than the host. realpath/abs_path
-# use the actual filesystem to resolve links.
-sub sanitize_mountpoint {
-    my ($mp) = @_;
-    $mp = '/' . $mp; # we always start with a slash
-    $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
-    $mp =~ s@/\./@@g; # collapse /./
-    $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
-    $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
-    $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
-    return $mp;
-}
 
 sub foreach_mountpoint_full {
     my ($conf, $reverse, $func) = @_;
@@ -1841,14 +2105,8 @@ sub foreach_mountpoint_full {
     foreach my $key (mountpoint_names($reverse)) {
        my $value = $conf->{$key};
        next if !defined($value);
-       my $mountpoint = parse_ct_mountpoint($value);
-
-       # just to be sure: rootfs is /
-       my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
-       $mountpoint->{mp} = sanitize_mountpoint($path);
-
-       $path = $mountpoint->{volume};
-       $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
+       my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
+       next if !defined($mountpoint);
 
        &$func($key, $mountpoint);
     }
@@ -1935,23 +2193,13 @@ sub mount_all {
        foreach_mountpoint($conf, sub {
            my ($ms, $mountpoint) = @_;
 
-           my $volid = $mountpoint->{volume};
-           my $mount = $mountpoint->{mp};
-
-           return if !$volid || !$mount;
-
-           my $image_path = PVE::Storage::path($storage_cfg, $volid);
-           my ($vtype, undef, undef, undef, undef, $isBase, $format) =
-               PVE::Storage::parse_volname($storage_cfg, $volid);
-
-           die "unable to mount base volume - internal error" if $isBase;
-
            mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
         });
     };
     if (my $err = $@) {
-       warn "mounting container failed - $err";
+       warn "mounting container failed\n";
        umount_all($vmid, $storage_cfg, $conf, 1);
+       die $err;
     }
 
     return $rootdir;
@@ -1973,12 +2221,64 @@ my $check_mount_path = sub {
     }
 };
 
+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;
+}
+
+# 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;
+    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 bindmount {
+    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;
+       }
+    }
+}
+
 # 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;
 
@@ -1996,6 +2296,14 @@ sub mountpoint_mount {
 
     die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
 
+    my $optstring = '';
+    if (defined($mountpoint->{acl})) {
+       $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl');
+    }
+    my $readonly = $mountpoint->{ro};
+
+    my @extra_opts = ('-o', $optstring);
+
     if ($storage) {
 
        my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
@@ -2004,50 +2312,66 @@ sub mountpoint_mount {
        my ($vtype, undef, undef, undef, undef, $isBase, $format) =
            PVE::Storage::parse_volname($storage_cfg, $volid);
 
+       $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
+
        if ($format eq 'subvol') {
            if ($mount_path) {
                if ($snapname) {
                    if ($scfg->{type} eq 'zfspool') {
                        my $path_arg = $path;
                        $path_arg =~ s!^/+!!;
-                       PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+                       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 {
-                   PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
+                   bindmount($path, $mount_path, $readonly, @extra_opts);
+                   warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
                }
            }
-           return wantarray ? ($path, 0) : $path;
-       } elsif ($format eq 'raw') {
+           return wantarray ? ($path, 0, $mounted_dev) : $path;
+       } elsif ($format eq 'raw' || $format eq 'iso') {
+           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;
-           my @extra_opts;
            if ($scfg->{path}) {
-               push @extra_opts, '-o', 'loop';
+               $mounted_dev = run_with_loopdev($domount, $path);
                $use_loopdev = 1;
-           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
-               # do nothing
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' ||
+                    $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') {
+               $mounted_dev = $path;
+               &$domount($path);
            } else {
                die "unsupported storage type '$scfg->{type}'\n";
            }
-           if ($mount_path) {
-               if ($isBase || defined($snapname)) {
-                   PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
-               } else {
-                   PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
-               }
-           }
-           return wantarray ? ($path, $use_loopdev) : $path;
+           return wantarray ? ($path, $use_loopdev, $mounted_dev) : $path;
        } else {
            die "unsupported image format '$format'\n";
        }
-    } elsif ($volid =~ m|^/dev/.+|) {
-       PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
-       return wantarray ? ($volid, 0) : $volid;
-    } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
+    } elsif ($type eq 'device') {
+                       push @extra_opts, '-o', 'ro' if $readonly;
+       PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0, $volid) : $volid;
+    } elsif ($type eq 'bind') {
+       die "directory '$volid' does not exist\n" if ! -d $volid;
        &$check_mount_path($volid);
-       PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
-       return wantarray ? ($volid, 0) : $volid;
+       bindmount($volid, $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";
@@ -2065,7 +2389,7 @@ sub get_vm_volumes {
 
        my $volid = $mountpoint->{volume};
 
-        return if !$volid || $volid =~ m|^/|;
+        return if !$volid || $mountpoint->{type} ne 'volume';
 
         my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
         return if !$sid;
@@ -2077,13 +2401,15 @@ sub get_vm_volumes {
 }
 
 sub mkfs {
-    my ($dev) = @_;
+    my ($dev, $rootuid, $rootgid) = @_;
 
-    PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
+    PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp',
+                            '-E', "root_owner=$rootuid:$rootgid",
+                            $dev]);
 }
 
 sub format_disk {
-    my ($storage_cfg, $volid) = @_;
+    my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
 
     if ($volid =~ m!^/dev/.+!) {
        mkfs($volid);
@@ -2104,7 +2430,7 @@ sub format_disk {
     die "cannot format volume '$volid' (format == $format)\n"
        if $format ne 'raw';
 
-    mkfs($path);
+    mkfs($path, $rootuid, $rootgid);
 }
 
 sub destroy_disks {
@@ -2122,6 +2448,9 @@ sub create_disks {
     my $vollist = [];
 
     eval {
+       my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+       my $chown_vollist = [];
+
        foreach_mountpoint($settings, sub {
            my ($ms, $mountpoint) = @_;
 
@@ -2130,9 +2459,7 @@ sub create_disks {
 
            my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-           return if !$storage;
-
-           if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
+           if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
                my ($storeid, $size_gb) = ($1, $2);
 
                my $size_kb = int(${size_gb}*1024) * 1024;
@@ -2144,35 +2471,46 @@ sub create_disks {
                    if ($size_kb > 0) {
                        $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
                                                           undef, $size_kb);
-                       format_disk($storecfg, $volid);
+                       format_disk($storecfg, $volid, $rootuid, $rootgid);
                    } else {
                        $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
                                                           undef, 0);
+                       push @$chown_vollist, $volid;
                    }
                } elsif ($scfg->{type} eq 'zfspool') {
 
                    $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
                                                       undef, $size_kb);
-               } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
+                   push @$chown_vollist, $volid;
+               } 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);
-                   format_disk($storecfg, $volid);
+                   format_disk($storecfg, $volid, $rootuid, $rootgid);
 
                } elsif ($scfg->{type} eq 'rbd') {
 
                    die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
                    $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
-                   format_disk($storecfg, $volid);
+                   format_disk($storecfg, $volid, $rootuid, $rootgid);
                } else {
                    die "unable to create containers on storage type '$scfg->{type}'\n";
                }
                push @$vollist, $volid;
-                my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
-               $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
+               $mountpoint->{volume} = $volid;
+               $mountpoint->{size} = $size_kb * 1024;
+               $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
            } else {
-               # use specified/existing volid
+                # use specified/existing volid/dir/device
+                $conf->{$ms} = 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);
     };
     # free allocated images on error
     if (my $err = $@) {
@@ -2242,4 +2580,45 @@ 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;
+       next if $key ne 'lxc.id_map';
+       if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
+           my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
+           push @$id_map, [$type, $ct, $host, $length];
+           if ($ct == 0) {
+               $rootuid = $host if $type eq 'u';
+               $rootgid = $host if $type eq 'g';
+           }
+       } else {
+           die "failed to parse id_map: $value\n";
+       }
+    }
+
+    if (!@$id_map && $conf->{unprivileged}) {
+       # Should we read them from /etc/subuid?
+       $id_map = [ ['u', '0', '100000', '65536'],
+                   ['g', '0', '100000', '65536'] ];
+       $rootuid = $rootgid = 100000;
+    }
+
+    return ($id_map, $rootuid, $rootgid);
+}
+
+sub userns_command {
+    my ($id_map) = @_;
+    if (@$id_map) {
+       return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];
+    }
+    return [];
+}
+
 1;