]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
warn when trying to delete non-existent options
[pve-container.git] / src / PVE / LXC.pm
index edc9b12ad9f34fdd174cc25822bcb30803eebb68..594a5ce7d71aabd717f32647ea3f764cc70a4438 100644 (file)
@@ -18,11 +18,20 @@ use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
 use PVE::Network;
 use PVE::AccessControl;
 use PVE::ProcFSTools;
+use Time::HiRes qw (gettimeofday);
 
 use Data::Dumper;
 
 my $nodename = PVE::INotify::nodename();
 
+my $cpuinfo= PVE::ProcFSTools::read_cpuinfo();
+
+our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
+                          '--xattrs',
+                          '--xattrs-include=user.*',
+                          '--xattrs-include=security.capability',
+                          '--warning=no-xattr-write' ];
+
 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
 
 my $rootfs_desc = {
@@ -40,8 +49,8 @@ my $rootfs_desc = {
     },
     size => {
        type => 'string',
+       format => 'disk-size',
        format_description => 'DiskSize',
-       pattern => '\d+[TGMK]?',
        description => 'Volume size (read only value).',
        optional => 1,
     },
@@ -183,6 +192,12 @@ my $confdesc = {
        description => "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
        default => 0,
     },
+    unprivileged => {
+       optional => 1,
+       type => 'boolean',
+       description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
+       default => 0,
+    },
 };
 
 my $valid_lxc_conf_keys = {
@@ -231,6 +246,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,
@@ -265,6 +281,7 @@ my $netconf_desc = {
        format_description => 'vmbr<Number>',
        description => 'Bridge to attach the network device to.',
        pattern => '[-_.\w\d]+',
+       optional => 1,
     },
     hwaddr => {
        type => 'string',
@@ -277,6 +294,7 @@ my $netconf_desc = {
        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 => {
@@ -344,6 +362,12 @@ my $mp_desc = {
 };
 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"} = {
@@ -354,6 +378,11 @@ for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
     };
 }
 
+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) = @_;
 
@@ -720,32 +749,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) = @_;
@@ -754,12 +780,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) || {};
@@ -767,10 +800,10 @@ 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 {
@@ -803,9 +836,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};
@@ -818,99 +854,99 @@ 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;
+       }
+
+       my $dtime = ($cdtime -  $old->{time}) * $cpucount * $cpuinfo->{user_hz};
+
+       if ($dtime > 1000) {
+           my $dutime = $used -  $old->{used};
+
+           $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus};
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => $d->{cpu},
+           };
+       } else {
+           $d->{cpu} = $old->{cpu};
        }
     }
-    return int($size);
-};
 
-my $format_size = sub {
-    my ($size) = @_;
+    my $netdev = PVE::ProcFSTools::read_proc_net_dev();
 
-    $size = int($size);
+    foreach my $dev (keys %$netdev) {
+       next if $dev !~ m/^veth([1-9]\d*)i/;
+       my $vmid = $1;
+       my $d = $list->{$vmid};
 
-    my $kb = int($size/1024);
-    return $size if $kb*1024 != $size;
+       next if !$d;
 
-    my $mb = int($kb/1024);
-    return "${kb}K" if $mb*1024 != $kb;
+       $d->{netout} += $netdev->{$dev}->{receive};
+       $d->{netin} += $netdev->{$dev}->{transmit};
 
-    my $gb = int($mb/1024);
-    return "${mb}M" if $gb*1024 != $mb;
+    }
 
-    return "${gb}G";
-};
+    return $list;
+}
+
+sub classify_mountpoint {
+    my ($vol) = @_;
+    if ($vol =~ m!^/!) {
+       return 'device' if $vol =~ m!^/dev/!;
+       return 'bind';
+    }
+    return 'volume';
+}
 
 sub parse_ct_mountpoint {
-    my ($data) = @_;
+    my ($data, $noerr) = @_;
 
     $data //= '';
 
     my $res;
     eval { $res = PVE::JSONSchema::parse_property_string($mp_desc, $data) };
     if ($@) {
-       warn $@;
-       return undef;
+       return undef if $noerr;
+       die $@;
     }
 
-    return undef if !defined($res->{volume});
-
-    if ($res->{size}) {
-       return undef if !defined($res->{size} = &$parse_size($res->{size}));
+    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) = @_;
-
-    my $opts = '';
-
-    die "missing volume\n" if !$info->{volume};
-
-    foreach my $o (qw(backup)) {
-       $opts .= ",$o=$info->{$o}" if defined($info->{$o});
-    }
-
-    if ($info->{size}) {
-       $opts .= ",size=" . &$format_size($info->{size});
-    }
-
-    $opts .= ",mp=$info->{mp}" if !$nomp;
-
-    return "$info->{volume}$opts";
+    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 {
@@ -920,11 +956,7 @@ sub parse_lxc_network {
 
     return $res if !$data;
 
-    eval { $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data) };
-    if ($@) {
-       warn $@;
-       return undef;
-    }
+    $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
 
     $res->{type} = 'veth';
     $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
@@ -984,56 +1016,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;
@@ -1072,13 +1068,27 @@ 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) {
        $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";
     }
 
+    $raw .= "lxc.start.unshare = 1\n";
+
+    # Should we read them from /etc/subuid?
+    if ($unprivileged && !$custom_idmap) {
+       $raw .= "lxc.id_map = u 0 100000 65536\n";
+       $raw .= "lxc.id_map = g 0 100000 65536\n";
+    }
+
     if (!has_dev_console($conf)) {
        $raw .= "lxc.console = none\n";
        $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
@@ -1087,6 +1097,9 @@ sub update_lxc_config {
     my $ttycount = get_tty_count($conf);
     $raw .= "lxc.tty = $ttycount\n";
 
+    # some init scripts expects a linux terminal (turnkey).
+    $raw .= "lxc.environment = TERM=linux\n";
+    
     my $utsname = $conf->{hostname} || "CT$vmid";
     $raw .= "lxc.utsname = $utsname\n";
 
@@ -1095,6 +1108,7 @@ sub update_lxc_config {
 
     my $lxcmem = int($memory*1024*1024);
     $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
+    $raw .= "lxc.cgroup.memory.kmem.limit_in_bytes = $lxcmem\n";
 
     my $lxcswap = int(($memory + $swap)*1024*1024);
     $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
@@ -1110,11 +1124,9 @@ sub update_lxc_config {
 
     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;
 
-    $raw .= "lxc.rootfs = $path\n";
+    $raw .= "lxc.rootfs = $dir/rootfs\n";
+    $raw .= "lxc.hook.stop = /usr/lib/x86_64-linux-gnu/lxc/hooks/unmount-namespace\n";
 
     my $netcount = 0;
     foreach my $k (keys %$conf) {
@@ -1169,12 +1181,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 "To many unused volume - 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) {
@@ -1182,8 +1215,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') {
@@ -1193,9 +1240,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;
@@ -1203,11 +1249,21 @@ 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 'unprivileged') {
+               die "unable to delete read-only option: '$opt'\n";
            } else {
                die "implement me"
            }
@@ -1244,23 +1300,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);
@@ -1277,28 +1329,37 @@ 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') {
            check_protection($conf, "can't update CT $vmid drive '$opt'");
            die "implement me: $opt";
+       } elsif ($opt eq 'unprivileged') {
+           die "unable to modify read-only option: '$opt'\n";
        } else {
            die "implement me: $opt";
        }
        write_config($vmid, $conf) if $running;
     }
 
-    if ($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 {
@@ -1353,7 +1414,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+$!!;
@@ -1363,20 +1424,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) = @_;
-
-       # skip bind mounts and block devices
-       if ($mountpoint->{volume} =~ m|^/|) {
-               return;
-       }
-
-       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";
@@ -1530,7 +1592,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;
@@ -1545,7 +1608,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
@@ -1718,20 +1786,19 @@ sub snapshot_create {
 
     my $conf = load_config($vmid);
 
-    my $cmd = "/usr/bin/lxc-freeze -n $vmid";
     my $running = check_running($vmid);
     eval {
        if ($running) {
-           PVE::Tools::run_command($cmd);
+           PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
+           PVE::Tools::run_command(['/bin/sync']);
        };
 
        my $storecfg = PVE::Storage::config();
        my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
        my $volid = $rootinfo->{volume};
 
-       $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
        if ($running) {
-           PVE::Tools::run_command($cmd);
+           PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
        };
 
        PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
@@ -1922,7 +1989,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);
+       my $mountpoint = parse_ct_mountpoint($value, 1);
+       next if !defined($mountpoint);
 
        # just to be sure: rootfs is /
        my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
@@ -2054,12 +2122,27 @@ 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;
+}
+
 # use $rootdir = undef to just return the corresponding mount path
 sub mountpoint_mount {
     my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
 
     my $volid = $mountpoint->{volume};
     my $mount = $mountpoint->{mp};
+    my $type = $mountpoint->{type};
     
     return if !$volid || !$mount;
 
@@ -2085,22 +2168,19 @@ 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]);
-                   } else {
+                   if ($scfg->{type} ne 'zfspool') {
                        die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
                    }
-               } else {
-                   PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
                }
+               PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
            }
            return wantarray ? ($path, 0) : $path;
-       } elsif ($format eq 'raw') {
+       } elsif ($format eq 'raw' || $format eq 'iso') {
            my $use_loopdev = 0;
            my @extra_opts;
            if ($scfg->{path}) {
@@ -2112,8 +2192,10 @@ sub mountpoint_mount {
                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]);
+               if ($format eq 'iso') {
+                   PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
+               } elsif ($isBase || defined($snapname)) {
+                   PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
                } else {
                    PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
                }
@@ -2122,10 +2204,10 @@ sub mountpoint_mount {
        } else {
            die "unsupported image format '$format'\n";
        }
-    } elsif ($volid =~ m|^/dev/.+|) {
+    } elsif ($type eq 'device') {
        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 'bind' && -d $volid) {
        &$check_mount_path($volid);
        PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
        return wantarray ? ($volid, 0) : $volid;
@@ -2146,7 +2228,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;
@@ -2158,13 +2240,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);
@@ -2185,7 +2269,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 {
@@ -2203,6 +2287,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) = @_;
 
@@ -2225,35 +2312,45 @@ 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);
+                   push @$chown_vollist, $volid;
                } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
 
                    $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
            }
        });
+
+       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 = $@) {
@@ -2323,4 +2420,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;