]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
VZDump lock update, drop lock_aquire/lock_release
[pve-container.git] / src / PVE / LXC.pm
index 4bea2f6a4652aaadd3328b903aa7645d67b530ef..df8bb28d6561aaf2a47e9b06e0d4432d01eec93e 100644 (file)
@@ -5,6 +5,8 @@ use warnings;
 use POSIX qw(EINTR);
 
 use File::Path;
+use File::Spec;
+use Cwd qw();
 use Fcntl ':flock';
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file);
@@ -12,348 +14,557 @@ use PVE::Storage;
 use PVE::SafeSyslog;
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option);
-use PVE::Tools qw($IPV6RE $IPV4RE);
+use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
 use PVE::Network;
+use PVE::AccessControl;
+use PVE::ProcFSTools;
+use Time::HiRes qw (gettimeofday);
 
 use Data::Dumper;
 
-cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
-
-PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
-sub verify_lxc_network {
-    my ($value, $noerr) = @_;
-
-    return $value if parse_lxc_network($value);
+my $nodename = PVE::INotify::nodename();
 
-    return undef if $noerr;
+my $cpuinfo= PVE::ProcFSTools::read_cpuinfo();
 
-    die "unable to parse network setting\n";
-}
+our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
+                          '--xattrs',
+                          '--xattrs-include=user.*',
+                          '--xattrs-include=security.capability',
+                          '--warning=no-xattr-write' ];
 
-my $nodename = PVE::INotify::nodename();
+cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
 
-sub parse_lxc_size {
-    my ($name, $value) = @_;
+my $rootfs_desc = {
+    volume => {
+       type => 'string',
+       default_key => 1,
+       format => 'pve-lxc-mp-string',
+       format_description => 'volume',
+       description => 'Volume, device or directory to mount into the container.',
+    },
+    backup => {
+       type => 'boolean',
+       format_description => '[1|0]',
+       description => 'Whether to include the mountpoint in backups.',
+       optional => 1,
+    },
+    size => {
+       type => 'string',
+       format => 'disk-size',
+       format_description => 'DiskSize',
+       description => 'Volume size (read only value).',
+       optional => 1,
+    },
+    acl => {
+       type => 'boolean',
+       format_description => 'acl',
+       description => 'Explicitly enable or disable ACL support.',
+       optional => 1,
+    },
+    ro => {
+       type => 'boolean',
+       format_description => 'ro',
+       description => 'Read-only mountpoint (not supported with bind mounts)',
+       optional => 1,
+    },
+};
 
-    if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
-       my ($res, $unit) = ($1, lc($2 || 'b'));
+PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
+    type => 'string', format => $rootfs_desc,
+    description => "Use volume as container root.",
+    optional => 1,
+});
 
-       return $res if $unit eq 'b';
-       return $res*1024 if $unit eq 'k';
-       return $res*1024*1024 if $unit eq 'm';
-       return $res*1024*1024*1024 if $unit eq 'g';
-    }
+PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
+    description => "The name of the snapshot.",
+    type => 'string', format => 'pve-configid',
+    maxLength => 40,
+});
 
-    return undef;
-}
+my $confdesc = {
+    lock => {
+       optional => 1,
+       type => 'string',
+       description => "Lock/unlock the VM.",
+       enum => [qw(migrate backup snapshot rollback)],
+    },
+    onboot => {
+       optional => 1,
+       type => 'boolean',
+       description => "Specifies whether a VM will be started during system bootup.",
+       default => 0,
+    },
+    startup => get_standard_option('pve-startup-order'),
+    template => {
+       optional => 1,
+       type => 'boolean',
+       description => "Enable/disable Template.",
+       default => 0,
+    },
+    arch => {
+       optional => 1,
+       type => 'string',
+       enum => ['amd64', 'i386'],
+       description => "OS architecture type.",
+       default => 'amd64',
+    },
+    ostype => {
+       optional => 1,
+       type => 'string',
+       enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'],
+       description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
+    },
+    console => {
+       optional => 1,
+       type => 'boolean',
+       description => "Attach a console device (/dev/console) to the container.",
+       default => 1,
+    },
+    tty => {
+       optional => 1,
+       type => 'integer',
+       description => "Specify the number of tty available to the container",
+       minimum => 0,
+       maximum => 6,
+       default => 2,
+    },
+    cpulimit => {
+       optional => 1,
+       type => 'number',
+       description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
+       minimum => 0,
+       maximum => 128,
+       default => 0,
+    },
+    cpuunits => {
+       optional => 1,
+       type => 'integer',
+       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+       minimum => 0,
+       maximum => 500000,
+       default => 1024,
+    },
+    memory => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of RAM for the VM in MB.",
+       minimum => 16,
+       default => 512,
+    },
+    swap => {
+       optional => 1,
+       type => 'integer',
+       description => "Amount of SWAP for the VM in MB.",
+       minimum => 0,
+       default => 512,
+    },
+    hostname => {
+       optional => 1,
+       description => "Set a host name for the container.",
+       type => 'string', format => 'dns-name',
+       maxLength => 255,
+    },
+    description => {
+       optional => 1,
+       type => 'string',
+       description => "Container description. Only used on the configuration web interface.",
+    },
+    searchdomain => {
+       optional => 1,
+       type => 'string', format => 'dns-name-list',
+       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
+    },
+    nameserver => {
+       optional => 1,
+       type => 'string', format => 'address-list',
+       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
+    },
+    rootfs => get_standard_option('pve-ct-rootfs'),
+    parent => {
+       optional => 1,
+       type => 'string', format => 'pve-configid',
+       maxLength => 40,
+       description => "Parent snapshot name. This is used internally, and should not be modified.",
+    },
+    snaptime => {
+       optional => 1,
+       description => "Timestamp for snapshots.",
+       type => 'integer',
+       minimum => 0,
+    },
+    cmode => {
+       optional => 1,
+       description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
+       type => 'string',
+       enum => ['shell', 'console', 'tty'],
+       default => 'tty',
+    },
+    protection => {
+       optional => 1,
+       type => 'boolean',
+       description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
+       default => 0,
+    },
+    unprivileged => {
+       optional => 1,
+       type => 'boolean',
+       description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
+       default => 0,
+    },
+};
 
-my $valid_lxc_keys = {
-    'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
+my $valid_lxc_conf_keys = {
     'lxc.include' => 1,
-    'lxc.rootfs' => 1,
-    'lxc.mount' => 1,
+    'lxc.arch' => 1,
     'lxc.utsname' => 1,
-
-    'lxc.id_map' => 1,
-
-    'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
-    'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
-    'lxc.cgroup.cpu.cfs_period_us' => '\d+',
-    'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
-    'lxc.cgroup.cpu.shares' => '\d+',
-
-    # mount related
-    'lxc.mount' => 1,
-    'lxc.mount.entry' => 1,
-    'lxc.mount.auto' => 1,
-
-    # not used by pve
-    'lxc.tty' => '\d+',
-    'lxc.pts' => 1,
     'lxc.haltsignal' => 1,
     'lxc.rebootsignal' => 1,
     'lxc.stopsignal' => 1,
     'lxc.init_cmd' => 1,
-    'lxc.console' => 1,
+    'lxc.network.type' => 1,
+    'lxc.network.flags' => 1,
+    'lxc.network.link' => 1,
+    'lxc.network.mtu' => 1,
+    'lxc.network.name' => 1,
+    'lxc.network.hwaddr' => 1,
+    'lxc.network.ipv4' => 1,
+    'lxc.network.ipv4.gateway' => 1,
+    'lxc.network.ipv6' => 1,
+    'lxc.network.ipv6.gateway' => 1,
+    'lxc.network.script.up' => 1,
+    'lxc.network.script.down' => 1,
+    'lxc.pts' => 1,
     'lxc.console.logfile' => 1,
+    'lxc.console' => 1,
+    'lxc.tty' => 1,
     'lxc.devttydir' => 1,
+    'lxc.hook.autodev' => 1,
     'lxc.autodev' => 1,
     'lxc.kmsg' => 1,
+    'lxc.mount' => 1,
+    'lxc.mount.entry' => 1,
+    'lxc.mount.auto' => 1,
+    'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
+    'lxc.rootfs.mount' => 1,
+    'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
+                            ', please use mountpoint options in the "rootfs" key',
+    # lxc.cgroup.*
     'lxc.cap.drop' => 1,
     'lxc.cap.keep' => 1,
     'lxc.aa_profile' => 1,
     'lxc.aa_allow_incomplete' => 1,
     'lxc.se_context' => 1,
-    'lxc.loglevel' => 1,
-    'lxc.logfile' => 1,
-    'lxc.environment' => 1,
-    'lxc.cgroup.devices.deny' => 1,
-
-    # autostart
-    'lxc.start.auto' => 1,
-    'lxc.start.delay' => 1,
-    'lxc.start.order' => 1,
-    'lxc.group' => 1,
-
-    # hooks
+    'lxc.seccomp' => 1,
+    'lxc.id_map' => 1,
     'lxc.hook.pre-start' => 1,
     'lxc.hook.pre-mount' => 1,
     'lxc.hook.mount' => 1,
-    'lxc.hook.autodev' => 1,
     'lxc.hook.start' => 1,
+    'lxc.hook.stop' => 1,
     'lxc.hook.post-stop' => 1,
     'lxc.hook.clone' => 1,
+    'lxc.hook.destroy' => 1,
+    'lxc.loglevel' => 1,
+    'lxc.logfile' => 1,
+    'lxc.start.auto' => 1,
+    'lxc.start.delay' => 1,
+    'lxc.start.order' => 1,
+    'lxc.group' => 1,
+    'lxc.environment' => 1,
+};
 
-    # pve related keys
-    'pve.nameserver' => sub {
-       my ($name, $value) = @_;
-       return verify_nameserver_list($value);
+my $netconf_desc = {
+    type => {
+       type => 'string',
+       optional => 1,
+       description => "Network interface type.",
+       enum => [qw(veth)],
+    },
+    name => {
+       type => 'string',
+       format_description => 'String',
+       description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
+       pattern => '[-_.\w\d]+',
     },
-    'pve.searchdomain' => sub {
-       my ($name, $value) = @_;
-       return verify_searchdomain_list($value);
+    bridge => {
+       type => 'string',
+       format_description => 'vmbr<Number>',
+       description => 'Bridge to attach the network device to.',
+       pattern => '[-_.\w\d]+',
+       optional => 1,
     },
-    'pve.onboot' => '(0|1)',
-    'pve.startup' => sub {
-       my ($name, $value) = @_;
-       return PVE::JSONSchema::pve_verify_startup_order($value);
+    hwaddr => {
+       type => 'string',
+       format_description => 'MAC',
+       description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
+       pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
+       optional => 1,
     },
-    'pve.comment' => 1,
-    'pve.disksize' => '\d+(\.\d+)?',
-    'pve.volid' => sub {
-       my ($name, $value) = @_;
-       PVE::Storage::parse_volume_id($value);
-       return $value;
+    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 $valid_lxc_network_keys = {
-    type => 1,
-    mtu => 1,
-    name => 1, # ifname inside container
-    'veth.pair' => 1, # ifname at host (eth${vmid}.X)
-    hwaddr => 1,
-};
+my $MAX_LXC_NETWORKS = 10;
+for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
+    $confdesc->{"net$i"} = {
+       optional => 1,
+       type => 'string', format => $netconf_desc,
+       description => "Specifies network interfaces for the container.",
+    };
+}
 
-my $valid_pve_network_keys = {
-    bridge => 1,
-    tag => 1,
-    firewall => 1,
-    ip => 1,
-    gw => 1,
-    ip6 => 1,
-    gw6 => 1,
+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 $lxc_array_configs = {
-    'lxc.network' => 1,
-    'lxc.mount' => 1,
-    'lxc.include' => 1,
-    'lxc.id_map' => 1,
-    'lxc.cgroup.devices.deny' => 1,
+my $unuseddesc = {
+    optional => 1,
+    type => 'string', format => 'pve-volume-id',
+    description => "Reference to unused volumes.",
 };
 
-sub write_lxc_config {
-    my ($filename, $data) = @_;
+my $MAX_MOUNT_POINTS = 10;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"mp$i"} = {
+       optional => 1,
+       type => 'string', format => $mp_desc,
+       description => "Use volume as container mount point (experimental feature).",
+       optional => 1,
+    };
+}
+
+my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"unused$i"} = $unuseddesc;
+}
 
-    my $raw = "";
+sub write_pct_config {
+    my ($filename, $conf) = @_;
 
-    return $raw if !$data;
+    delete $conf->{snapstate}; # just to be sure
 
-    my $done_hash = { digest => 1};
+    my $generate_raw_config = sub {
+       my ($conf) = @_;
 
-    my $dump_entry = sub {
-       my ($k) = @_;
-       my $value = $data->{$k};
-       return if !defined($value);
-       return if $done_hash->{$k};
-       $done_hash->{$k} = 1;
-       if (ref($value)) {
-           die "got unexpected reference for '$k'"
-               if !$lxc_array_configs->{$k};
-           foreach my $v (@$value) {
-               $raw .= "$k = $v\n";
-           }
-       } else {
-           $raw .= "$k = $value\n";
+       my $raw = '';
+
+       # add description as comment to top of file
+       my $descr = $conf->{description} || '';
+       foreach my $cl (split(/\n/, $descr)) {
+           $raw .= '#' .  PVE::Tools::encode_text($cl) . "\n";
        }
-    };
 
-    # Note: Order is important! Include defaults first, so that we
-    # can overwrite them later.
-    &$dump_entry('lxc.include');
-
-    foreach my $k (sort keys %$data) {
-       next if $k !~ m/^lxc\./;
-       &$dump_entry($k);
-    }
-
-    foreach my $k (sort keys %$data) {
-       next if $k !~ m/^pve\./;
-       &$dump_entry($k);
-    }
-
-    my $network_count = 0;
-    foreach my $k (sort keys %$data) {
-       next if $k !~ m/^net\d+$/;
-       $done_hash->{$k} = 1;
-       my $net = $data->{$k};
-       $network_count++;
-       $raw .= "lxc.network.type = $net->{type}\n";
-       foreach my $subkey (sort keys %$net) {
-           next if $subkey eq 'type';
-           if ($valid_lxc_network_keys->{$subkey}) {
-               $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
-           } elsif ($valid_pve_network_keys->{$subkey}) {
-               $raw .= "pve.network.$subkey = $net->{$subkey}\n";
-           } else {
-               die "found invalid network key '$subkey'";
+       foreach my $key (sort keys %$conf) {
+           next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || 
+               $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
+           my $value = $conf->{$key};
+           die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
+           $raw .= "$key: $value\n";
+       }
+
+       if (my $lxcconf = $conf->{lxc}) {
+           foreach my $entry (@$lxcconf) {
+               my ($k, $v) = @$entry;
+               $raw .= "$k: $v\n";
            }
        }
-    }
+       
+       return $raw;
+    };
 
-    if (!$network_count) {
-       $raw .= "lxc.network.type = empty\n";
-    }
+    my $raw = &$generate_raw_config($conf);
 
-    foreach my $k (sort keys %$data) {
-       next if $done_hash->{$k};
-       die "found un-written value in config - implement this!";
+    foreach my $snapname (sort keys %{$conf->{snapshots}}) {
+       $raw .= "\n[$snapname]\n";
+       $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
     }
 
     return $raw;
 }
 
-sub parse_lxc_option {
-    my ($name, $value) = @_;
+sub check_type {
+    my ($key, $value) = @_;
 
-    my $parser = $valid_lxc_keys->{$name};
+    die "unknown setting '$key'\n" if !$confdesc->{$key};
 
-    die "invalid key '$name'\n" if !defined($parser);
+    my $type = $confdesc->{$key}->{type};
+
+    if (!defined($value)) {
+       die "got undefined value\n";
+    }
+
+    if ($value =~ m/[\n\r]/) {
+       die "property contains a line feed\n";
+    }
 
-    if ($parser eq '1') {
+    if ($type eq 'boolean') {
+       return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
+       return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
+       die "type check ('boolean') failed - got '$value'\n";
+    } elsif ($type eq 'integer') {
+       return int($1) if $value =~ m/^(\d+)$/;
+       die "type check ('integer') failed - got '$value'\n";
+    } elsif ($type eq 'number') {
+        return $value if $value =~ m/^(\d+)(\.\d+)?$/;
+        die "type check ('number') failed - got '$value'\n";
+    } elsif ($type eq 'string') {
+       if (my $fmt = $confdesc->{$key}->{format}) {
+           PVE::JSONSchema::check_format($fmt, $value);
+           return $value;
+       }
        return $value;
-    } elsif (ref($parser)) {
-       my $res = &$parser($name, $value);
-       return $res if defined($res);
     } else {
-       # assume regex
-       return $value if $value =~ m/^$parser$/;
+       die "internal error"
     }
-
-    die "unable to parse value '$value' for option '$name'\n";
 }
 
-sub parse_lxc_config {
+sub parse_pct_config {
     my ($filename, $raw) = @_;
 
     return undef if !defined($raw);
 
-    my $data = {
+    my $res = {
        digest => Digest::SHA::sha1_hex($raw),
+       snapshots => {},
     };
 
-    $filename =~ m|/lxc/(\d+)/config$|
+    $filename =~ m|/lxc/(\d+).conf$|
        || die "got strange filename '$filename'";
 
     my $vmid = $1;
 
-    my $network_counter = 0;
-    my $network_list = [];
-    my $host_ifnames = {};
-
-     my $find_next_hostif_name = sub {
-       for (my $i = 0; $i < 10; $i++) {
-           my $name = "veth${vmid}.$i";
-           if (!$host_ifnames->{$name}) {
-               $host_ifnames->{$name} = 1;
-               return $name;
-           }
-       }
-
-       die "unable to find free host_ifname"; # should not happen
-    };
-
-    my $push_network = sub {
-       my ($netconf) = @_;
-       return if !$netconf;
-       push @{$network_list}, $netconf;
-       $network_counter++;
-       if (my $netname = $netconf->{'veth.pair'}) {
-           if ($netname =~ m/^veth(\d+).(\d)$/) {
-               die "wrong vmid for network interface pair\n" if $1 != $vmid;
-               my $host_ifnames->{$netname} = 1;
-           } else {
-               die "wrong network interface pair\n";
-           }
-       }
-    };
-
-    my $network;
+    my $conf = $res;
+    my $descr = '';
+    my $section = '';
 
-    while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
-       my $line = $1;
-
-       next if $line =~ m/^\#/;
+    my @lines = split(/\n/, $raw);
+    foreach my $line (@lines) {
        next if $line =~ m/^\s*$/;
 
-       if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-           my ($subkey, $value) = ($1, $2);
-           if ($subkey eq 'type') {
-               &$push_network($network);
-               $network = { type => $value };
-           } elsif ($valid_lxc_network_keys->{$subkey}) {
-               $network->{$subkey} = $value;
-           } else {
-               die "unable to parse config line: $line\n";
-           }
-           next;
-       }
-       if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-           my ($subkey, $value) = ($1, $2);
-           if ($valid_pve_network_keys->{$subkey}) {
-               $network->{$subkey} = $value;
-           } else {
-               die "unable to parse config line: $line\n";
-           }
+       if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
+           $section = $1;
+           $conf->{description} = $descr if $descr;
+           $descr = '';
+           $conf = $res->{snapshots}->{$section} = {};
            next;
        }
-       if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
-           my ($name, $value) = ($1, $2);
-           $data->{$name} = $value;
+
+       if ($line =~ m/^\#(.*)\s*$/) {
+           $descr .= PVE::Tools::decode_text($1) . "\n";
            next;
        }
-       if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
-           my ($name, $value) = ($1, $2);
 
-           if ($lxc_array_configs->{$name}) {
-               $data->{$name} = [] if !defined($data->{$name});
-               push @{$data->{$name}},  parse_lxc_option($name, $value);
+       if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
+           my $key = $1;
+           my $value = $3;
+           my $validity = $valid_lxc_conf_keys->{$key} || 0;
+           if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
+               push @{$conf->{lxc}}, [$key, $value];
+           } elsif (my $errmsg = $validity) {
+               warn "vm $vmid - $key: $errmsg\n";
            } else {
-               die "multiple definitions for $name\n" if defined($data->{$name});
-               $data->{$name} = parse_lxc_option($name, $value);
+               warn "vm $vmid - unable to parse config: $line\n";
            }
-
-           next;
+       } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
+           $descr .= PVE::Tools::decode_text($2);
+       } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
+           $conf->{snapstate} = $1;
+       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
+           my $key = $1;
+           my $value = $2;
+           eval { $value = check_type($key, $value); };
+           warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
+           $conf->{$key} = $value;
+       } else {
+           warn "vm $vmid - unable to parse config: $line\n";
        }
-
-       die "unable to parse config line: $line\n";
     }
 
-    &$push_network($network);
+    $conf->{description} = $descr if $descr;
 
-    foreach my $net (@{$network_list}) {
-       next if $net->{type} eq 'empty'; # skip
-       $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
-       $net->{hwaddr} =  PVE::Tools::random_ether_addr() if !$net->{hwaddr};
-       die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
-
-       if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
-           $data->{"net$1"} = $net;
-       }
-    }
+    delete $res->{snapstate}; # just to be sure
 
-    return $data;
+    return $res;
 }
 
 sub config_list {
@@ -376,7 +587,7 @@ sub cfs_config_path {
     my ($vmid, $node) = @_;
 
     $node = $nodename if !$node;
-    return "nodes/$node/lxc/$vmid/config";
+    return "nodes/$node/lxc/$vmid.conf";
 }
 
 sub config_file {
@@ -387,12 +598,13 @@ sub config_file {
 }
 
 sub load_config {
-    my ($vmid) = @_;
+    my ($vmid, $node) = @_;
 
-    my $cfspath = cfs_config_path($vmid);
+    $node = $nodename if !$node;
+    my $cfspath = cfs_config_path($vmid, $node);
 
     my $conf = PVE::Cluster::cfs_read_file($cfspath);
-    die "container $vmid does not exists\n" if !defined($conf);
+    die "container $vmid does not exist\n" if !defined($conf);
 
     return $conf;
 }
@@ -403,17 +615,13 @@ sub create_config {
     my $dir = "/etc/pve/nodes/$nodename/lxc";
     mkdir $dir;
 
-    $dir .= "/$vmid";
-    mkdir($dir) || die "unable to create container configuration directory - $!\n";
-
     write_config($vmid, $conf);
 }
 
 sub destroy_config {
     my ($vmid) = @_;
 
-    my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
-    File::Path::rmtree($dir);
+    unlink config_file($vmid, $nodename);
 }
 
 sub write_config {
@@ -424,188 +632,34 @@ sub write_config {
     PVE::Cluster::cfs_write_file($cfspath, $conf);
 }
 
-my $tempcounter = 0;
-sub write_temp_config {
-    my ($vmid, $conf) = @_;
+# flock: we use one file handle per process, so lock file
+# can be called multiple times and will succeed for the same process.
 
-    $tempcounter++;
-    my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
-
-    my $raw =  write_lxc_config($filename, $conf);
-
-    PVE::Tools::file_set_contents($filename, $raw);
-
-    return $filename;
-}
-
-# flock: we use one file handle per process, so lock file
-# can be called multiple times and succeeds for the same process.
-
-my $lock_handles =  {};
-my $lockdir = "/run/lock/lxc";
+my $lock_handles =  {};
+my $lockdir = "/run/lock/lxc";
 
 sub lock_filename {
     my ($vmid) = @_;
 
-    return "$lockdir/pve-config-{$vmid}.lock";
+    return "$lockdir/pve-config-${vmid}.lock";
 }
 
-sub lock_aquire {
-    my ($vmid, $timeout) = @_;
+sub lock_container {
+    my ($vmid, $timeout, $code, @param) = @_;
 
     $timeout = 10 if !$timeout;
-    my $mode = LOCK_EX;
 
     my $filename = lock_filename($vmid);
 
     mkdir $lockdir if !-d $lockdir;
 
-    my $lock_func = sub {
-       if (!$lock_handles->{$$}->{$filename}) {
-           my $fh = new IO::File(">>$filename") ||
-               die "can't open file - $!\n";
-           $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
-       }
-
-       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";
-           }
-
-           $lock_handles->{$$}->{$filename}->{refcount}++;
-
-           print STDERR " OK\n";
-       }
-    };
-
-    eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
-    my $err = $@;
-    if ($err) {
-       die "can't lock file '$filename' - $err";
-    }
-}
-
-sub lock_release {
-    my ($vmid) = @_;
-
-    my $filename = lock_filename($vmid);
-
-    if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
-       my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
-       if ($refcount <= 0) {
-           $lock_handles->{$$}->{$filename} = undef;
-           close ($fh);
-       }
-    }
-}
-
-sub lock_container {
-    my ($vmid, $timeout, $code, @param) = @_;
-
-    my $res;
-
-    lock_aquire($vmid, $timeout);
-    eval { $res = &$code(@param) };
-    my $err = $@;
-    lock_release($vmid);
+    my $res = PVE::Tools::lock_file_full($filename, $timeout, 0, $code, @param);
 
-    die $err if $err;
+    die $@ if $@;
 
     return $res;
 }
 
-my $confdesc = {
-    onboot => {
-       optional => 1,
-       type => 'boolean',
-       description => "Specifies whether a VM will be started during system bootup.",
-       default => 0,
-    },
-    startup => get_standard_option('pve-startup-order'),
-    cpulimit => {
-       optional => 1,
-       type => 'number',
-       description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
-       minimum => 0,
-       maximum => 128,
-       default => 0,
-    },
-    cpuunits => {
-       optional => 1,
-       type => 'integer',
-       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
-       minimum => 0,
-       maximum => 500000,
-       default => 1000,
-    },
-    memory => {
-       optional => 1,
-       type => 'integer',
-       description => "Amount of RAM for the VM in MB.",
-       minimum => 16,
-       default => 512,
-    },
-    swap => {
-       optional => 1,
-       type => 'integer',
-       description => "Amount of SWAP for the VM in MB.",
-       minimum => 0,
-       default => 512,
-    },
-    disk => {
-       optional => 1,
-       type => 'number',
-       description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
-       minimum => 0,
-       default => 4,
-    },
-    hostname => {
-       optional => 1,
-       description => "Set a host name for the container.",
-       type => 'string',
-       maxLength => 255,
-    },
-    description => {
-       optional => 1,
-       type => 'string',
-       description => "Container description. Only used on the configuration web interface.",
-    },
-    searchdomain => {
-       optional => 1,
-       type => 'string',
-       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
-    },
-    nameserver => {
-       optional => 1,
-       type => 'string',
-       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
-    },
-};
-
-my $MAX_LXC_NETWORKS = 10;
-for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
-    $confdesc->{"net$i"} = {
-       optional => 1,
-       type => 'string', format => 'pve-lxc-network',
-       description => "Specifies network interfaces for the container.\n\n".
-           "The string should have the follow format:\n\n".
-           "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
-           "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
-           ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
-           ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
-    };
-}
-
 sub option_exists {
     my ($name) = @_;
 
@@ -617,6 +671,20 @@ sub json_config_properties {
     my $prop = shift;
 
     foreach my $opt (keys %$confdesc) {
+       next if $opt eq 'parent' || $opt eq 'snaptime';
+       next if $prop->{$opt};
+       $prop->{$opt} = $confdesc->{$opt};
+    }
+
+    return $prop;
+}
+
+sub json_config_properties_no_rootfs {
+    my $prop = shift;
+
+    foreach my $opt (keys %$confdesc) {
+       next if $prop->{$opt};
+       next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
        $prop->{$opt} = $confdesc->{$opt};
     }
 
@@ -638,7 +706,7 @@ sub list_active_containers {
     while (defined(my $line = <$fh>)) {
        if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
            my $path = $1;
-           if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
+           if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
                $res->{$1} = 1;
            }
        }
@@ -661,32 +729,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) = @_;
@@ -695,50 +760,47 @@ 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) || {};
 
-       $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
+       $d->{name} = $conf->{'hostname'} || "CT$vmid";
        $d->{name} =~ s/[\s]//g;
 
-       $d->{cpus} = 0;
-
-       my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
-       my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
-
-       if ($cfs_period_us && $cfs_quota_us) {
-           $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
-       }
+       $d->{cpus} = $conf->{cpulimit} || $cpucount;
 
-       $d->{disk} = 0;
-       $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
-           int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
-
-       if (my $private = $conf->{'lxc.rootfs'}) {
-           if ($private =~ m!^/!) {
-               my $res = PVE::Tools::df($private, 2);
-               $d->{disk} = $res->{used};
-               $d->{maxdisk} = $res->{total};
-           } elsif ($running) {
-               if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
-                   my $res = get_container_disk_usage($vmid);
-                   $d->{disk} = $res->{used};
-                   $d->{maxdisk} = $res->{total};
-               }
+       if ($d->{pid}) {
+           my $res = get_container_disk_usage($vmid, $d->{pid});
+           $d->{disk} = $res->{used};
+           $d->{maxdisk} = $res->{total};
+       } else {
+           $d->{disk} = 0;
+           # use 4GB by default ??
+           if (my $rootfs = $conf->{rootfs}) {
+               my $rootinfo = parse_ct_rootfs($rootfs);
+               $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
+           } else {
+               $d->{maxdisk} = 4*1024*1024*1024;
            }
        }
 
        $d->{mem} = 0;
        $d->{swap} = 0;
-       $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
-           ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
+       $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
+       $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
 
        $d->{uptime} = 0;
        $d->{cpu} = 0;
@@ -748,13 +810,18 @@ sub vmstatus {
 
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
+
+       $d->{template} = is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
-       next if $d->{status} ne 'running';
+       my $pid = $d->{pid};
 
-       $d->{uptime} = 100; # fixme:
+       next if !$pid; # skip stopped CTs
+
+       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};
@@ -767,27 +834,117 @@ sub vmstatus {
                $d->{diskwrite} = $2 if $key eq 'Write';
            }
        }
+
+       my $pstat = &$parse_cpuacct_stat($vmid);
+
+       my $used = $pstat->{utime} + $pstat->{stime};
+
+       my $old = $last_proc_vmid_stat->{$vmid};
+       if (!$old) {
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => 0,
+           };
+           next;
+       }
+
+       my $dtime = ($cdtime -  $old->{time}) * $cpucount * $cpuinfo->{user_hz};
+
+       if ($dtime > 1000) {
+           my $dutime = $used -  $old->{used};
+
+           $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus};
+           $last_proc_vmid_stat->{$vmid} = {
+               time => $cdtime,
+               used => $used,
+               cpu => $d->{cpu},
+           };
+       } else {
+           $d->{cpu} = $old->{cpu};
+       }
+    }
+
+    my $netdev = PVE::ProcFSTools::read_proc_net_dev();
+
+    foreach my $dev (keys %$netdev) {
+       next if $dev !~ m/^veth([1-9]\d*)i/;
+       my $vmid = $1;
+       my $d = $list->{$vmid};
+
+       next if !$d;
+
+       $d->{netout} += $netdev->{$dev}->{receive};
+       $d->{netin} += $netdev->{$dev}->{transmit};
+
     }
 
     return $list;
 }
 
+sub classify_mountpoint {
+    my ($vol) = @_;
+    if ($vol =~ m!^/!) {
+       return 'device' if $vol =~ m!^/dev/!;
+       return 'bind';
+    }
+    return 'volume';
+}
 
-sub print_lxc_network {
-    my $net = shift;
+my $parse_ct_mountpoint_full = sub {
+    my ($desc, $data, $noerr) = @_;
 
-    die "no network name defined\n" if !$net->{name};
+    $data //= '';
 
-    my $res = "name=$net->{name}";
+    my $res;
+    eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
+    if ($@) {
+       return undef if $noerr;
+       die $@;
+    }
 
-    foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
-       next if !defined($net->{$k});
-       $res .= ",$k=$net->{$k}";
+    if (defined(my $size = $res->{size})) {
+       $size = PVE::JSONSchema::parse_size($size);
+       if (!defined($size)) {
+           return undef if $noerr;
+           die "invalid size: $size\n";
+       }
+       $res->{size} = $size;
     }
 
+    $res->{type} = classify_mountpoint($res->{volume});
+
+    return $res;
+};
+
+sub parse_ct_rootfs {
+    my ($data, $noerr) = @_;
+
+    my $res =  &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
+
+    $res->{mp} = '/' if defined($res);
+
     return $res;
 }
 
+sub parse_ct_mountpoint {
+    my ($data, $noerr) = @_;
+
+    return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
+}
+
+sub print_ct_mountpoint {
+    my ($info, $nomp) = @_;
+    my $skip = [ 'type' ];
+    push @$skip, 'mp' if $nomp;
+    return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
+}
+
+sub print_lxc_network {
+    my $net = shift;
+    return PVE::JSONSchema::print_property_string($net, $netconf_desc);
+}
+
 sub parse_lxc_network {
     my ($data) = @_;
 
@@ -795,13 +952,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};
@@ -839,7 +990,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+$/;
@@ -861,56 +1012,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;
@@ -918,60 +1033,120 @@ sub parse_ipv4_cidr {
     die "unable to parse ipv4 address/mask\n";
 }
 
-sub lxc_conf_to_pve {
-    my ($vmid, $lxc_conf) = @_;
+sub check_lock {
+    my ($conf) = @_;
 
-    my $properties = json_config_properties();
+    die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
+}
 
-    my $conf = { digest => $lxc_conf->{digest} };
+sub check_protection {
+    my ($vm_conf, $err_msg) = @_;
 
-    foreach my $k (keys %$properties) {
+    if ($vm_conf->{protection}) {
+       die "$err_msg - protection mode enabled\n";
+    }
+}
 
-       if ($k eq 'description') {
-           if (my $raw = $lxc_conf->{'pve.comment'}) {
-               $conf->{$k} = PVE::Tools::decode_text($raw);
-           }
-       } elsif ($k eq 'onboot') {
-           $conf->{$k} = $lxc_conf->{'pve.onboot'} if  $lxc_conf->{'pve.onboot'};
-       } elsif ($k eq 'startup') {
-           $conf->{$k} = $lxc_conf->{'pve.startup'} if  $lxc_conf->{'pve.startup'};
-       } elsif ($k eq 'hostname') {
-           $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
-       } elsif ($k eq 'nameserver') {
-           $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
-       } elsif ($k eq 'searchdomain') {
-           $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
-       } elsif ($k eq 'memory') {
-           if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
-               $conf->{$k} = int($value / (1024*1024));
-           }
-       } elsif ($k eq 'swap') {
-           if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
-               my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
-               $conf->{$k} = int(($value -$mem) / (1024*1024));
-           }
-       } elsif ($k eq 'cpulimit') {
-           my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
-           my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
+sub update_lxc_config {
+    my ($storage_cfg, $vmid, $conf) = @_;
 
-           if ($cfs_period_us && $cfs_quota_us) {
-               $conf->{$k} = $cfs_quota_us/$cfs_period_us;
-           } else {
-               $conf->{$k} = 0;
-           }
-       } elsif ($k eq 'cpuunits') {
-           $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
-       } elsif ($k eq 'disk') {
-           $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
-               $lxc_conf->{'pve.disksize'} : 0;
-       } elsif ($k =~ m/^net\d$/) {
-           my $net = $lxc_conf->{$k};
-           next if !$net;
-           $conf->{$k} = print_lxc_network($net);
+    my $dir = "/var/lib/lxc/$vmid";
+
+    if ($conf->{template}) {
+
+       unlink "$dir/config";
+
+       return;
+    }
+
+    my $raw = '';
+
+    die "missing 'arch' - internal error" if !$conf->{arch};
+    $raw .= "lxc.arch = $conf->{arch}\n";
+
+    my $unprivileged = $conf->{unprivileged};
+    my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
+
+    my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
+    if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
+       $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
+       if ($unprivileged || $custom_idmap) {
+           $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
        }
+    } else {
+       die "implement me (ostype $ostype)";
     }
 
-    return $conf;
+    $raw .= "lxc.monitor.unshare = 1\n";
+
+    # Should we read them from /etc/subuid?
+    if ($unprivileged && !$custom_idmap) {
+       $raw .= "lxc.id_map = u 0 100000 65536\n";
+       $raw .= "lxc.id_map = g 0 100000 65536\n";
+    }
+
+    if (!has_dev_console($conf)) {
+       $raw .= "lxc.console = none\n";
+       $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
+    }
+
+    my $ttycount = get_tty_count($conf);
+    $raw .= "lxc.tty = $ttycount\n";
+
+    # some init scripts expect a linux terminal (turnkey).
+    $raw .= "lxc.environment = TERM=linux\n";
+    
+    my $utsname = $conf->{hostname} || "CT$vmid";
+    $raw .= "lxc.utsname = $utsname\n";
+
+    my $memory = $conf->{memory} || 512;
+    my $swap = $conf->{swap} // 0;
+
+    my $lxcmem = int($memory*1024*1024);
+    $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
+
+    my $lxcswap = int(($memory + $swap)*1024*1024);
+    $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
+
+    if (my $cpulimit = $conf->{cpulimit}) {
+       $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
+       my $value = int(100000*$cpulimit);
+       $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
+    }
+
+    my $shares = $conf->{cpuunits} || 1024;
+    $raw .= "lxc.cgroup.cpu.shares = $shares\n";
+
+    my $mountpoint = parse_ct_rootfs($conf->{rootfs});
+
+    $raw .= "lxc.rootfs = $dir/rootfs\n";
+
+    my $netcount = 0;
+    foreach my $k (keys %$conf) {
+       next if $k !~ m/^net(\d+)$/;
+       my $ind = $1;
+       my $d = parse_lxc_network($conf->{$k});
+       $netcount++;
+       $raw .= "lxc.network.type = veth\n";
+       $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
+       $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
+       $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
+       $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
+    }
+
+    if (my $lxcconf = $conf->{lxc}) {
+       foreach my $entry (@$lxcconf) {
+           my ($k, $v) = @$entry;
+           $netcount++ if $k eq 'lxc.network.type';
+           $raw .= "$k = $v\n";
+       }
+    }
+
+    $raw .= "lxc.network.type = empty\n" if !$netcount;
+    
+    File::Path::mkpath("$dir/rootfs");
+
+    PVE::Tools::file_set_contents("$dir/config", $raw);
 }
 
 # verify and cleanup nameserver list (replace \0 with ' ')
@@ -999,150 +1174,295 @@ sub verify_searchdomain_list {
     return join(' ', @list);
 }
 
-sub update_lxc_config {
+sub add_unused_volume {
+    my ($config, $volid) = @_;
+
+    my $key;
+    for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
+       my $test = "unused$ind";
+       if (my $vid = $config->{$test}) {
+           return if $vid eq $volid; # do not add duplicates
+       } else {
+           $key = $test;
+       }
+    }
+
+    die "Too many unused volumes - please delete them first.\n" if !$key;
+
+    $config->{$key} = $volid;
+
+    return $key;
+}
+
+sub update_pct_config {
     my ($vmid, $conf, $running, $param, $delete) = @_;
 
     my @nohotplug;
 
+    my $new_disks = 0;
+    my @deleted_volumes;
+
     my $rootdir;
     if ($running) {
        my $pid = find_lxc_pid($vmid);
        $rootdir = "/proc/$pid/root";
     }
 
+    my $hotplug_error = sub {
+       if ($running) {
+           push @nohotplug, @_;
+           return 1;
+       } else {
+           return 0;
+       }
+    };
+
     if (defined($delete)) {
        foreach my $opt (@$delete) {
-           if ($opt eq 'hostname' || $opt eq 'memory') {
+           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') {
-               delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
+               delete $conf->{$opt};
                write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
-           } elsif ($opt eq 'description') {
-               delete $conf->{'pve.comment'};
-           } elsif ($opt eq 'onboot') {
-               delete $conf->{'pve.onboot'};
-           } elsif ($opt eq 'startup') {
-               delete $conf->{'pve.startup'};
-           } elsif ($opt eq 'nameserver') {
-               delete $conf->{'pve.nameserver'};
-               push @nohotplug, $opt;
-               next if $running;
-           } elsif ($opt eq 'searchdomain') {
-               delete $conf->{'pve.searchdomain'};
-               push @nohotplug, $opt;
-               next if $running;
+           } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
+               delete $conf->{$opt};
+           } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
+                    $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+               next if $hotplug_error->($opt);
+               delete $conf->{$opt};
            } elsif ($opt =~ m/^net(\d)$/) {
                delete $conf->{$opt};
                next if !$running;
                my $netid = $1;
-               PVE::Network::veth_delete("veth${vmid}.$netid");
-           } else {
-               die "implement me"
+               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};
+           } elsif ($opt eq 'unprivileged') {
+               die "unable to delete read-only option: '$opt'\n";
+           } else {
+               die "implement me (delete: $opt)"
            }
-           PVE::LXC::write_config($vmid, $conf) if $running;
+           write_config($vmid, $conf) if $running;
        }
     }
 
+    # There's no separate swap size to configure, there's memory and "total"
+    # memory (iow. memory+swap). This means we have to change them together.
+    my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
+    my $wanted_swap =  PVE::Tools::extract_param($param, 'swap');
+    if (defined($wanted_memory) || defined($wanted_swap)) {
+
+       my $old_memory = ($conf->{memory} || 512);
+       my $old_swap = ($conf->{swap} || 0);
+
+       $wanted_memory //= $old_memory;
+       $wanted_swap //= $old_swap;
+
+        my $total = $wanted_memory + $wanted_swap;
+       if ($running) {
+           my $old_total = $old_memory + $old_swap;
+           if ($total > $old_total) {
+               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
+               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
+           } else {
+               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
+               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
+           }
+       }
+       $conf->{memory} = $wanted_memory;
+       $conf->{swap} = $wanted_swap;
+
+       write_config($vmid, $conf) if $running;
+    }
+
     foreach my $opt (keys %$param) {
        my $value = $param->{$opt};
        if ($opt eq 'hostname') {
-           $conf->{'lxc.utsname'} = $value;
+           $conf->{$opt} = $value;
        } elsif ($opt eq 'onboot') {
-           $conf->{'pve.onboot'} = $value ? 1 : 0;
+           $conf->{$opt} = $value ? 1 : 0;
        } elsif ($opt eq 'startup') {
-           $conf->{'pve.startup'} = $value;
+           $conf->{$opt} = $value;
+       } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+           next if $hotplug_error->($opt);
+           $conf->{$opt} = $value;
        } elsif ($opt eq 'nameserver') {
+           next if $hotplug_error->($opt);
            my $list = verify_nameserver_list($value);
-           $conf->{'pve.nameserver'} = $list;
-           push @nohotplug, $opt;
-           next if $running;
+           $conf->{$opt} = $list;
        } elsif ($opt eq 'searchdomain') {
+           next if $hotplug_error->($opt);
            my $list = verify_searchdomain_list($value);
-           $conf->{'pve.searchdomain'} = $list;
-           push @nohotplug, $opt;
-           next if $running;
-       } elsif ($opt eq 'memory') {
-           $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
-           write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
-       } elsif ($opt eq 'swap') {
-           my $mem =  $conf->{'lxc.cgroup.memory.limit_in_bytes'};
-           $mem = $param->{memory}*1024*1024 if $param->{memory};
-           $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
-           write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
-
+           $conf->{$opt} = $list;
        } elsif ($opt eq 'cpulimit') {
-           if ($value > 0) {
-               my $cfs_period_us = 100000;
-               $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
-               $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
-               write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
-           } else {
-               delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
-               delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
-               write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
-           }
+           next if $hotplug_error->($opt); # FIXME: hotplug
+           $conf->{$opt} = $value;
        } elsif ($opt eq 'cpuunits') {
-           $conf->{'lxc.cgroup.cpu.shares'} = $value;
+           $conf->{$opt} = $value;
            write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
        } elsif ($opt eq 'description') {
-           $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
-       } elsif ($opt eq 'disk') {
-           $conf->{'pve.disksize'} = $value;
-           push @nohotplug, $opt;
-           next if $running;
+           $conf->{$opt} = PVE::Tools::encode_text($value);
        } elsif ($opt =~ m/^net(\d+)$/) {
            my $netid = $1;
-           my $net = PVE::LXC::parse_lxc_network($value);
-           $net->{'veth.pair'} = "veth${vmid}.$netid";
-           if (!$running) {
-               $conf->{$opt} = $net;
+           my $net = parse_lxc_network($value);
+           if (!$running) {
+               $conf->{$opt} = print_lxc_network($net);
            } else {
                update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
            }
+       } elsif ($opt eq 'protection') {
+           $conf->{$opt} = $value ? 1 : 0;
+        } elsif ($opt =~ m/^mp(\d+)$/) {
+           next if $hotplug_error->($opt);
+           check_protection($conf, "can't update CT $vmid drive '$opt'");
+           $conf->{$opt} = $value;
+           $new_disks = 1;
+        } elsif ($opt eq 'rootfs') {
+           check_protection($conf, "can't update CT $vmid drive '$opt'");
+           die "implement me: $opt";
+       } elsif ($opt eq 'unprivileged') {
+           die "unable to modify read-only option: '$opt'\n";
        } else {
-           die "implement me"
+           die "implement me: $opt";
+       }
+       write_config($vmid, $conf) if $running;
+    }
+
+    if (@deleted_volumes) {
+       my $storage_cfg = PVE::Storage::config();
+       foreach my $volume (@deleted_volumes) {
+           delete_mountpoint_volume($storage_cfg, $vmid, $volume);
        }
-       PVE::LXC::write_config($vmid, $conf) if $running;
     }
 
+    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 {
+    my ($conf) = @_;
+
+    return !(defined($conf->{console}) && !$conf->{console});
+}
+       
+sub get_tty_count {
+    my ($conf) = @_;
+
+    return $conf->{tty} // $confdesc->{tty}->{default};
+}
+
+sub get_cmode {
+    my ($conf) = @_;
+
+    return $conf->{cmode} // $confdesc->{cmode}->{default};
+}
+
+sub get_console_command {
+    my ($vmid, $conf) = @_;
+
+    my $cmode = get_cmode($conf);
+
+    if ($cmode eq 'console') {
+       return ['lxc-console', '-n',  $vmid, '-t', 0];
+    } elsif ($cmode eq 'tty') {
+       return ['lxc-console', '-n',  $vmid];
+    } elsif ($cmode eq 'shell') {
+       return ['lxc-attach', '--clear-env', '-n', $vmid];
+    } else {
+       die "internal error";
+    }
+}
+
 sub get_primary_ips {
     my ($conf) = @_;
 
     # return data from net0
 
-    my $net = $conf->{net0};
-    return undef if !$net;
+    return undef if !defined($conf->{net0});
+    my $net = parse_lxc_network($conf->{net0});
 
     my $ipv4 = $net->{ip};
-    $ipv4 =~ s!/\d+$!! if $ipv4;
-    my $ipv6 = $net->{ip};
-    $ipv6 =~ s!/\d+$!! if $ipv6;
+    if ($ipv4) {
+       if ($ipv4 =~ /^(dhcp|manual)$/) {
+           $ipv4 = undef
+       } else {
+           $ipv4 =~ s!/\d+$!!;
+       }
+    }
+    my $ipv6 = $net->{ip6};
+    if ($ipv6) {
+       if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
+           $ipv6 = undef;
+       } else {
+           $ipv6 =~ s!/\d+$!!;
+       }
+    }
 
     return ($ipv4, $ipv6);
 }
 
-sub destory_lxc_container {
-    my ($storage_cfg, $vmid, $conf) = @_;
+sub delete_mountpoint_volume {
+    my ($storage_cfg, $vmid, $volume) = @_;
 
-    if (my $volid = $conf->{'pve.volid'}) {
+    return if classify_mountpoint($volume) ne 'volume';
 
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
-       die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
+    my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
+    PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
+}
 
-       PVE::Storage::vdisk_free($storage_cfg, $volid);
+sub destroy_lxc_container {
+    my ($storage_cfg, $vmid, $conf) = @_;
 
-       destroy_config($vmid);
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+       delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
+    });
 
-    } else {
-       my $cmd = ['lxc-destroy', '-n', $vmid ];
+    rmdir "/var/lib/lxc/$vmid/rootfs";
+    unlink "/var/lib/lxc/$vmid/config";
+    rmdir "/var/lib/lxc/$vmid";
+    destroy_config($vmid);
 
-       PVE::Tools::run_command($cmd);
-    }
+    #my $cmd = ['lxc-destroy', '-n', $vmid ];
+    #PVE::Tools::run_command($cmd);
+}
+
+sub vm_stop_cleanup {
+    my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
+    
+    eval {
+       if (!$keepActive) {
+
+            my $vollist = get_vm_volumes($conf);
+           PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
+       }
+    };
+    warn $@ if $@; # avoid errors - just warn
 }
 
 my $safe_num_ne = sub {
@@ -1168,54 +1488,62 @@ my $safe_string_ne = sub {
 sub update_net {
     my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
 
-    my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth . "p";
+    if ($newnet->{type} ne 'veth') {
+       # for when there are physical interfaces
+       die "cannot update interface of type $newnet->{type}";
+    }
+
+    my $veth = "veth${vmid}i${netid}";
     my $eth = $newnet->{name};
 
-    if ($conf->{$opt}) {
-       if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
-           &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
+    if (my $oldnetcfg = $conf->{$opt}) {
+       my $oldnet = parse_lxc_network($oldnetcfg);
+
+       if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
+           &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
 
-            PVE::Network::veth_delete($veth);
+           PVE::Network::veth_delete($veth);
            delete $conf->{$opt};
-           PVE::LXC::write_config($vmid, $conf);
+           write_config($vmid, $conf);
 
-           hotplug_net($vmid, $conf, $opt, $newnet);
+           hotplug_net($vmid, $conf, $opt, $newnet, $netid);
 
-       } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
-                &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
-                &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
+       } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+                &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
+                &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
 
-               if ($conf->{$opt}->{bridge}){
+               if ($oldnet->{bridge}) {
                    PVE::Network::tap_unplug($veth);
-                   delete $conf->{$opt}->{bridge};
-                   delete $conf->{$opt}->{tag};
-                   delete $conf->{$opt}->{firewall};
-                   PVE::LXC::write_config($vmid, $conf);
+                   foreach (qw(bridge tag firewall)) {
+                       delete $oldnet->{$_};
+                   }
+                   $conf->{$opt} = print_lxc_network($oldnet);
+                   write_config($vmid, $conf);
                }
 
-                PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
-               $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
-               $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
-               $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
-               PVE::LXC::write_config($vmid, $conf);
+               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
+               foreach (qw(bridge tag firewall)) {
+                   $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
+               }
+               $conf->{$opt} = print_lxc_network($oldnet);
+               write_config($vmid, $conf);
        }
     } else {
-       hotplug_net($vmid, $conf, $opt, $newnet);
+       hotplug_net($vmid, $conf, $opt, $newnet, $netid);
     }
 
     update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
 }
 
 sub hotplug_net {
-    my ($vmid, $conf, $opt, $newnet) = @_;
+    my ($vmid, $conf, $opt, $newnet, $netid) = @_;
 
-    my $veth = $newnet->{'veth.pair'};
+    my $veth = "veth${vmid}i${netid}";
     my $vethpeer = $veth . "p";
     my $eth = $newnet->{name};
 
     PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
-    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
 
     # attach peer in container
     my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
@@ -1225,49 +1553,50 @@ sub hotplug_net {
     $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up'  ];
     PVE::Tools::run_command($cmd);
 
-    $conf->{$opt}->{type} = 'veth';
-    $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
-    $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
-    $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
-    $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
-    $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
-    $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
-
-    delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
-    delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
-    delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
-    delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
+    my $done = { type => 'veth' };
+    foreach (qw(bridge tag firewall hwaddr name)) {
+       $done->{$_} = $newnet->{$_} if $newnet->{$_};
+    }
+    $conf->{$opt} = print_lxc_network($done);
 
-    PVE::LXC::write_config($vmid, $conf);
+    write_config($vmid, $conf);
 }
 
 sub update_ipconfig {
     my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
 
-    my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
+    my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
 
-    my $optdata = $conf->{$opt};
+    my $optdata = parse_lxc_network($conf->{$opt});
     my $deleted = [];
     my $added = [];
-    my $netcmd = sub {
-       my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
-       PVE::Tools::run_command($cmd);
+    my $nscmd = sub {
+       my $cmdargs = shift;
+       PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
     };
+    my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
 
     my $change_ip_config = sub {
-       my ($family_opt, $suffix) = @_;
-       $suffix = '' if !$suffix;
+       my ($ipversion) = @_;
+
+       my $family_opt = "-$ipversion";
+       my $suffix = $ipversion == 4 ? '' : $ipversion;
        my $gw= "gw$suffix";
        my $ip= "ip$suffix";
 
-       my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
-       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
+       my $newip = $newnet->{$ip};
+       my $newgw = $newnet->{$gw};
+       my $oldip = $optdata->{$ip};
+
+       my $change_ip = &$safe_string_ne($oldip, $newip);
+       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
 
        return if !$change_ip && !$change_gw;
 
        # step 1: add new IP, if this fails we cancel
-       if ($change_ip && $newnet->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
+       my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
+       if ($change_ip && $is_real_ip) {
+           eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
            if (my $err = $@) {
                warn $err;
                return;
@@ -1280,31 +1609,50 @@ sub update_ipconfig {
        #   errors. The config is then saved.
        # Note: 'ip route replace' can add
        if ($change_gw) {
-           if ($newnet->{$gw}) {
-               eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
+           if ($newgw) {
+               eval {
+                   if ($is_real_ip && !PVE::Network::is_ip_in_cidr($newgw, $newip, $ipversion)) {
+                       &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
+                   }
+                   &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
+               };
                if (my $err = $@) {
                    warn $err;
                    # the route was not replaced, the old IP is still available
                    # rollback (delete new IP) and cancel
                    if ($change_ip) {
-                       eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
+                       eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
                        warn $@ if $@; # no need to die here
                    }
                    return;
                }
            } else {
-               eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
+               eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
                # if the route was not deleted, the guest might have deleted it manually
                # warn and continue
                warn $@ if $@;
            }
        }
 
-       # from this point on we safe the configuration
+       # from this point on we save the configuration
        # step 3: delete old IP ignoring errors
-       if ($change_ip && $optdata->{$ip}) {
-           eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
+       if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
+           # We need to enable promote_secondaries, otherwise our newly added
+           # address will be removed along with the old one.
+           my $promote = 0;
+           eval {
+               if ($ipversion == 4) {
+                   &$nscmd({ outfunc => sub { $promote = int(shift) } },
+                           'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
+                   &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
+               }
+               &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
+           };
            warn $@ if $@; # no need to die here
+
+           if ($ipversion == 4) {
+               &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
+           }
        }
 
        foreach my $property ($ip, $gw) {
@@ -1314,12 +1662,824 @@ sub update_ipconfig {
                delete $optdata->{$property};
            }
        }
-       PVE::LXC::write_config($vmid, $conf);
+       $conf->{$opt} = print_lxc_network($optdata);
+       write_config($vmid, $conf);
        $lxc_setup->setup_network($conf);
     };
 
-    &$change_ip_config('-4');
-    &$change_ip_config('-6', '6');
+    &$change_ip_config(4);
+    &$change_ip_config(6);
+
+}
+
+# Internal snapshots
+
+# NOTE: Snapshot create/delete involves several non-atomic
+# actions, and can take a long time.
+# So we try to avoid locking the file and use the 'lock' variable
+# inside the config file instead.
+
+my $snapshot_copy_config = sub {
+    my ($source, $dest) = @_;
+
+    foreach my $k (keys %$source) {
+       next if $k eq 'snapshots';
+       next if $k eq 'snapstate';
+       next if $k eq 'snaptime';
+       next if $k eq 'vmstate';
+       next if $k eq 'lock';
+       next if $k eq 'digest';
+       next if $k eq 'description';
+
+       $dest->{$k} = $source->{$k};
+    }
+};
+
+my $snapshot_prepare = sub {
+    my ($vmid, $snapname, $comment) = @_;
+
+    my $snap;
+
+    my $updatefn =  sub {
+
+       my $conf = load_config($vmid);
+
+       die "you can't take a snapshot if it's a template\n"
+           if is_template($conf);
+
+       check_lock($conf);
+
+       $conf->{lock} = 'snapshot';
+
+       die "snapshot name '$snapname' already used\n"
+           if defined($conf->{snapshots}->{$snapname});
+
+       my $storecfg = PVE::Storage::config();
+       my $feature = $snapname eq 'vzdump' ? 'vzdump' : 'snapshot';
+       die "snapshot feature is not available\n" if !has_feature($feature, $conf, $storecfg);
+
+       $snap = $conf->{snapshots}->{$snapname} = {};
+
+       &$snapshot_copy_config($conf, $snap);
+
+       $snap->{'snapstate'} = "prepare";
+       $snap->{'snaptime'} = time();
+       $snap->{'description'} = $comment if $comment;
+       $conf->{snapshots}->{$snapname} = $snap;
+
+       write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    return $snap;
+};
+
+my $snapshot_commit = sub {
+    my ($vmid, $snapname) = @_;
+
+    my $updatefn = sub {
+
+       my $conf = load_config($vmid);
+
+       die "missing snapshot lock\n"
+           if !($conf->{lock} && $conf->{lock} eq 'snapshot');
+
+       die "snapshot '$snapname' does not exist\n"
+           if !defined($conf->{snapshots}->{$snapname});
+
+       die "wrong snapshot state\n"
+           if !($conf->{snapshots}->{$snapname}->{'snapstate'} && 
+                $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
+
+       delete $conf->{snapshots}->{$snapname}->{'snapstate'};
+       delete $conf->{lock};
+       $conf->{parent} = $snapname;
+
+       write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10 ,$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);
+
+       # TODO: implement support for mountpoints
+       die "unable to handle mountpoint '$ms' - feature not implemented\n"
+           if $ms ne 'rootfs';
+    });
+
+    return $err ? 0 : 1;
+}
+
+sub snapshot_create {
+    my ($vmid, $snapname, $comment) = @_;
+
+    my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
+
+    my $conf = load_config($vmid);
+
+    my $running = check_running($vmid);
+    
+    my $unfreeze = 0;
+    
+    eval {
+       if ($running) {
+           PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
+           $unfreeze = 1;
+           PVE::Tools::run_command(['/bin/sync']);
+       };
+
+       my $storecfg = PVE::Storage::config();
+       my $rootinfo = parse_ct_rootfs($conf->{rootfs});
+       my $volid = $rootinfo->{volume};
+
+       PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
+       &$snapshot_commit($vmid, $snapname);
+    };
+    my $err = $@;
+    
+    if ($unfreeze) {
+       eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
+       warn $@ if $@;
+    }
+    
+    if ($err) {
+       snapshot_delete($vmid, $snapname, 1);
+       die "$err\n";
+    }
+}
+
+sub snapshot_delete {
+    my ($vmid, $snapname, $force) = @_;
+
+    my $snap;
+
+    my $conf;
+
+    my $updatefn =  sub {
+
+       $conf = load_config($vmid);
+
+       die "you can't delete a snapshot if vm is a template\n"
+           if is_template($conf);
+
+       $snap = $conf->{snapshots}->{$snapname};
+
+       check_lock($conf);
+
+       die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+       $snap->{snapstate} = 'delete';
+
+       write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    my $storecfg = PVE::Storage::config();
+
+    my $unlink_parent = sub {
+
+       my ($confref, $new_parent) = @_;
+
+       if ($confref->{parent} && $confref->{parent} eq $snapname) {
+           if ($new_parent) {
+               $confref->{parent} = $new_parent;
+           } else {
+               delete $confref->{parent};
+           }
+       }
+    };
+
+    my $del_snap =  sub {
+
+       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};
+
+       write_config($vmid, $conf);
+    };
+
+    my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
+    my $rootinfo = parse_ct_rootfs($rootfs);
+    my $volid = $rootinfo->{volume};
+
+    eval {
+       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
+    };
+    my $err = $@;
+
+    if(!$err || ($err && $force)) {
+       lock_container($vmid, 10, $del_snap);
+       if ($err) {
+           die "Can't delete snapshot: $vmid $snapname $err\n";
+       }
+    }
+}
+
+sub snapshot_rollback {
+    my ($vmid, $snapname) = @_;
+
+    my $storecfg = PVE::Storage::config();
+
+    my $conf = load_config($vmid);
+
+    die "you can't rollback if vm is a template\n" if is_template($conf);
+
+    my $snap = $conf->{snapshots}->{$snapname};
+
+    die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+    my $rootfs = $snap->{rootfs};
+    my $rootinfo = parse_ct_rootfs($rootfs);
+    my $volid = $rootinfo->{volume};
+
+    PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
+
+    my $updatefn = sub {
+
+       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" 
+           if $snap->{snapstate};
+
+       check_lock($conf);
+
+       system("lxc-stop -n $vmid --kill") if check_running($vmid);
+
+       die "unable to rollback vm $vmid: vm is running\n"
+           if check_running($vmid);
+
+       $conf->{lock} = 'rollback';
+
+       my $forcemachine;
+
+       # copy snapshot config to current config
+
+       my $tmp_conf = $conf;
+       &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
+       $conf->{snapshots} = $tmp_conf->{snapshots};
+       delete $conf->{snaptime};
+       delete $conf->{snapname};
+       $conf->{parent} = $snapname;
+
+       write_config($vmid, $conf);
+    };
+
+    my $unlockfn = sub {
+       delete $conf->{lock};
+       write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
+
+    lock_container($vmid, 5, $unlockfn);
+}
+
+sub template_create {
+    my ($vmid, $conf) = @_;
+
+    my $storecfg = PVE::Storage::config();
+
+    my $rootinfo = parse_ct_rootfs($conf->{rootfs});
+    my $volid = $rootinfo->{volume};
+
+    die "Template feature is not available for '$volid'\n"
+       if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
+
+    PVE::Storage::activate_volumes($storecfg, [$volid]);
+
+    my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
+    $rootinfo->{volume} = $template_volid;
+    $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
+
+    write_config($vmid, $conf);
+}
+
+sub is_template {
+    my ($conf) = @_;
+
+    return 1 if defined $conf->{template} && $conf->{template} == 1;
+}
+
+sub mountpoint_names {
+    my ($reverse) = @_;
+
+    my @names = ('rootfs');
+
+    for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+       push @names, "mp$i";
+    }
+
+    return $reverse ? reverse @names : @names;
+}
+
+
+sub foreach_mountpoint_full {
+    my ($conf, $reverse, $func) = @_;
+
+    foreach my $key (mountpoint_names($reverse)) {
+       my $value = $conf->{$key};
+       next if !defined($value);
+       my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
+       next if !defined($mountpoint);
+
+       &$func($key, $mountpoint);
+    }
+}
+
+sub foreach_mountpoint {
+    my ($conf, $func) = @_;
+
+    foreach_mountpoint_full($conf, 0, $func);
+}
+
+sub foreach_mountpoint_reverse {
+    my ($conf, $func) = @_;
+
+    foreach_mountpoint_full($conf, 1, $func);
+}
+
+sub check_ct_modify_config_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
+
+    return 1 if $authuser ne 'root@pam';
+
+    foreach my $opt (@$key_list) {
+
+       if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
+       } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
+       } elsif ($opt eq 'memory' || $opt eq 'swap') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
+       } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
+                $opt eq 'searchdomain' || $opt eq 'hostname') {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
+       } else {
+           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
+       }
+    }
+
+    return 1;
+}
+
+sub umount_all {
+    my ($vmid, $storage_cfg, $conf, $noerr) = @_;
+
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    my $volid_list = get_vm_volumes($conf);
+
+    foreach_mountpoint_reverse($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       my $volid = $mountpoint->{volume};
+       my $mount = $mountpoint->{mp};
+
+       return if !$volid || !$mount;
+
+       my $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
+
+       return if !PVE::ProcFSTools::is_mounted($mount_path);
+
+       eval {
+           PVE::Tools::run_command(['umount', '-d', $mount_path]);
+       };
+       if (my $err = $@) {
+           if ($noerr) {
+               warn $err;
+           } else {
+               die $err;
+           }
+       }
+    });
+}
+
+sub mount_all {
+    my ($vmid, $storage_cfg, $conf) = @_;
+
+    my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+    File::Path::make_path($rootdir);
+
+    my $volid_list = get_vm_volumes($conf);
+    PVE::Storage::activate_volumes($storage_cfg, $volid_list);
+
+    eval {
+       foreach_mountpoint($conf, sub {
+           my ($ms, $mountpoint) = @_;
+
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+        });
+    };
+    if (my $err = $@) {
+       warn "mounting container failed\n";
+       umount_all($vmid, $storage_cfg, $conf, 1);
+       die $err;
+    }
+
+    return $rootdir;
+}
+
+
+sub mountpoint_mount_path {
+    my ($mountpoint, $storage_cfg, $snapname) = @_;
+
+    return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
+}
+
+my $check_mount_path = sub {
+    my ($path) = @_;
+    $path = File::Spec->canonpath($path);
+    my $real = Cwd::realpath($path);
+    if ($real ne $path) {
+       die "mount path modified by symlink: $path != $real";
+    }
+};
+
+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;
+
+    my $mount_path;
+    
+    if (defined($rootdir)) {
+       $rootdir =~ s!/+$!!;
+       $mount_path = "$rootdir/$mount";
+       $mount_path =~ s!/+!/!g;
+       &$check_mount_path($mount_path);
+       File::Path::mkpath($mount_path);
+    }
+    
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+    die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
+
+    my $optstring = '';
+    if (defined($mountpoint->{acl})) {
+       $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl');
+    }
+    if ($mountpoint->{ro}) {
+       $optstring .= ',' if $optstring;
+       $optstring .= 'ro';
+    }
+
+    my @extra_opts = ('-o', $optstring);
+
+    if ($storage) {
+
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
+       my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
+
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
+
+       $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
+
+       if ($format eq 'subvol') {
+           if ($mount_path) {
+               if ($snapname) {
+                   if ($scfg->{type} eq 'zfspool') {
+                       my $path_arg = $path;
+                       $path_arg =~ s!^/+!!;
+                       PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
+                   } else {
+                       die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
+                   }
+               } else {
+                   if ($mountpoint->{ro}) {
+                       die "read-only bind mounts not supported\n";
+                   }
+                   PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
+               }
+           }
+           return wantarray ? ($path, 0) : $path;
+       } elsif ($format eq 'raw' || $format eq 'iso') {
+           my $use_loopdev = 0;
+           if ($scfg->{path}) {
+               push @extra_opts, '-o', 'loop';
+               $use_loopdev = 1;
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' ||
+                    $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') {
+               # do nothing
+           } else {
+               die "unsupported storage type '$scfg->{type}'\n";
+           }
+           if ($mount_path) {
+               if ($format eq 'iso') {
+                   PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
+               } elsif ($isBase || defined($snapname)) {
+                   PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
+               } else {
+                   PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+               }
+           }
+           return wantarray ? ($path, $use_loopdev) : $path;
+       } else {
+           die "unsupported image format '$format'\n";
+       }
+    } elsif ($type eq 'device') {
+       PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0) : $volid;
+    } elsif ($type eq 'bind') {
+       if ($mountpoint->{ro}) {
+           die "read-only bind mounts not supported\n";
+           # Theoretically we'd have to execute both:
+           # mount -o bind $a $b
+           # mount -o bind,remount,ro $a $b
+       }
+       die "directory '$volid' does not exist\n" if ! -d $volid;
+       &$check_mount_path($volid);
+       PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0) : $volid;
+    }
+    
+    die "unsupported storage";
+}
+
+sub get_vm_volumes {
+    my ($conf, $excludes) = @_;
+
+    my $vollist = [];
+
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       return if $excludes && $ms eq $excludes;
+
+       my $volid = $mountpoint->{volume};
+
+        return if !$volid || $mountpoint->{type} ne 'volume';
+
+        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+        return if !$sid;
+
+        push @$vollist, $volid;
+    });
+
+    return $vollist;
+}
+
+sub mkfs {
+    my ($dev, $rootuid, $rootgid) = @_;
+
+    PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp',
+                            '-E', "root_owner=$rootuid:$rootgid",
+                            $dev]);
+}
+
+sub format_disk {
+    my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
+
+    if ($volid =~ m!^/dev/.+!) {
+       mkfs($volid);
+       return;
+    }
+
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+    die "cannot format volume '$volid' with no storage\n" if !$storage;
+
+    PVE::Storage::activate_volumes($storage_cfg, [$volid]);
+
+    my $path = PVE::Storage::path($storage_cfg, $volid);
+
+    my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+       PVE::Storage::parse_volname($storage_cfg, $volid);
+
+    die "cannot format volume '$volid' (format == $format)\n"
+       if $format ne 'raw';
+
+    mkfs($path, $rootuid, $rootgid);
+}
+
+sub destroy_disks {
+    my ($storecfg, $vollist) = @_;
+
+    foreach my $volid (@$vollist) {
+       eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+       warn $@ if $@;
+    }
+}
+
+sub create_disks {
+    my ($storecfg, $vmid, $settings, $conf) = @_;
+
+    my $vollist = [];
+
+    eval {
+       my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+       my $chown_vollist = [];
+
+       foreach_mountpoint($settings, sub {
+           my ($ms, $mountpoint) = @_;
+
+           my $volid = $mountpoint->{volume};
+           my $mp = $mountpoint->{mp};
+
+           my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+           if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
+               my ($storeid, $size_gb) = ($1, $2);
+
+               my $size_kb = int(${size_gb}*1024) * 1024;
+
+               my $scfg = PVE::Storage::storage_config($storecfg, $storage);
+               # fixme: use better naming ct-$vmid-disk-X.raw?
+
+               if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+                   if ($size_kb > 0) {
+                       $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
+                                                          undef, $size_kb);
+                       format_disk($storecfg, $volid, $rootuid, $rootgid);
+                   } else {
+                       $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                          undef, 0);
+                       push @$chown_vollist, $volid;
+                   }
+               } elsif ($scfg->{type} eq 'zfspool') {
+
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                      undef, $size_kb);
+                   push @$chown_vollist, $volid;
+               } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') {
+
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+                   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, $rootuid, $rootgid);
+               } else {
+                   die "unable to create containers on storage type '$scfg->{type}'\n";
+               }
+               push @$vollist, $volid;
+               $mountpoint->{volume} = $volid;
+               $mountpoint->{size} = $size_kb * 1024;
+               $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+           } else {
+                # use specified/existing volid/dir/device
+                $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+           }
+       });
+
+       PVE::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 = $@) {
+       destroy_disks($storecfg, $vollist);
+        die $err;
+    }
+    return $vollist;
+}
+
+# bash completion helper
+
+sub complete_os_templates {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Storage::config();
+
+    my $storeid;
+
+    if ($cvalue =~ m/^([^:]+):/) {
+       $storeid = $1;
+    }
+
+    my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
+    my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
+
+    my $res = [];
+    foreach my $id (keys %$data) {
+       foreach my $item (@{$data->{$id}}) {
+           push @$res, $item->{volid} if defined($item->{volid});
+       }
+    }
+
+    return $res;
+}
+
+my $complete_ctid_full = sub {
+    my ($running) = @_;
+
+    my $idlist = vmstatus();
+
+    my $active_hash = list_active_containers();
+
+    my $res = [];
+
+    foreach my $id (keys %$idlist) {
+       my $d = $idlist->{$id};
+       if (defined($running)) {
+           next if $d->{template};
+           next if $running && !$active_hash->{$id};
+           next if !$running && $active_hash->{$id};
+       }
+       push @$res, $id;
+
+    }
+    return $res;
+};
+
+sub complete_ctid {
+    return &$complete_ctid_full();
+}
+
+sub complete_ctid_stopped {
+    return &$complete_ctid_full(0);
+}
+
+sub complete_ctid_running {
+    return &$complete_ctid_full(1);
+}
+
+sub parse_id_maps {
+    my ($conf) = @_;
+
+    my $id_map = [];
+    my $rootuid = 0;
+    my $rootgid = 0;
+
+    my $lxc = $conf->{lxc};
+    foreach my $entry (@$lxc) {
+       my ($key, $value) = @$entry;
+       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;