]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
specify data format for hostname, searchdomain and nameserver
[pve-container.git] / src / PVE / LXC.pm
index ad751fffc7c5d3954d7e0c2e45938580a0c2a2cc..3e1e5aa8c543abcda3331b418bc748e57c9cf7c3 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,8 +14,10 @@ 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 Data::Dumper;
 
@@ -46,10 +50,16 @@ sub verify_ct_mountpoint {
 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
     type => 'string', format => 'pve-ct-mountpoint',
     typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
-    description => "Use volume as container root. You can use special '<storage>:<size>' syntax for create/restore, where size specifies the disk size in GB (for example 'local:5.5' to create 5.5GB image on storage 'local').",
+    description => "Use volume as container root.",
     optional => 1,
 });
 
+PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
+    description => "The name of the snapshot.",
+    type => 'string', format => 'pve-configid',
+    maxLength => 40,
+});
+
 my $confdesc = {
     lock => {
        optional => 1,
@@ -64,6 +74,12 @@ my $confdesc = {
        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',
@@ -74,16 +90,22 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-       enum => ['debian', 'ubuntu', 'centos'],
+       enum => ['debian', 'ubuntu', 'centos', '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 => 4,
+       default => 2,
     },
     cpulimit => {
        optional => 1,
@@ -99,7 +121,7 @@ my $confdesc = {
        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,
+       default => 1024,
     },
     memory => {
        optional => 1,
@@ -118,7 +140,7 @@ my $confdesc = {
     hostname => {
        optional => 1,
        description => "Set a host name for the container.",
-       type => 'string',
+       type => 'string', format => 'dns-name',
        maxLength => 255,
     },
     description => {
@@ -128,12 +150,12 @@ my $confdesc = {
     },
     searchdomain => {
        optional => 1,
-       type => 'string',
+       type => 'string', format => 'dns-name-list',
        description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
     },
     nameserver => {
        optional => 1,
-       type => 'string',
+       type => 'string', format => 'address-list',
        description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
     },
     rootfs => get_standard_option('pve-ct-rootfs'),
@@ -149,6 +171,81 @@ my $confdesc = {
        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 remove operation.",
+       default => 0,
+    },
+};
+
+my $valid_lxc_conf_keys = {
+    'lxc.include' => 1,
+    'lxc.arch' => 1,
+    'lxc.utsname' => 1,
+    'lxc.haltsignal' => 1,
+    'lxc.rebootsignal' => 1,
+    'lxc.stopsignal' => 1,
+    'lxc.init_cmd' => 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' => 1,
+    'lxc.rootfs.mount' => 1,
+    'lxc.rootfs.options' => 1,
+    # lxc.cgroup.*
+    'lxc.cap.drop' => 1,
+    'lxc.cap.keep' => 1,
+    'lxc.aa_profile' => 1,
+    'lxc.aa_allow_incomplete' => 1,
+    'lxc.se_context' => 1,
+    'lxc.seccomp' => 1,
+    'lxc.id_map' => 1,
+    'lxc.hook.pre-start' => 1,
+    'lxc.hook.pre-mount' => 1,
+    'lxc.hook.mount' => 1,
+    'lxc.hook.start' => 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,
+    'lxc.' => 1,
+    'lxc.' => 1,
+    'lxc.' => 1,
+    'lxc.' => 1,
 };
 
 my $MAX_LXC_NETWORKS = 10;
@@ -165,6 +262,17 @@ for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
     };
 }
 
+my $MAX_MOUNT_POINTS = 10;
+for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
+    $confdesc->{"mp$i"} = {
+       optional => 1,
+       type => 'string', format => 'pve-ct-mountpoint',
+       typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
+       description => "Use volume as container mount point (experimental feature).",
+       optional => 1,
+    };
+}
+
 sub write_pct_config {
     my ($filename, $conf) = @_;
 
@@ -183,9 +291,19 @@ sub write_pct_config {
 
        foreach my $key (sort keys %$conf) {
            next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || 
-               $key eq 'snapshots' || $key eq 'snapname';
-           $raw .= "$key: $conf->{$key}\n";
+               $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;
     };
 
@@ -271,7 +389,15 @@ sub parse_pct_config {
            next;
        }
 
-       if ($line =~ m/^(description):\s*(.*\S)\s*$/) {
+       if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
+           my $key = $1;
+           my $value = $3;
+           if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
+               push @{$conf->{lxc}}, [$key, $value];
+           } else {
+               warn "vm $vmid - unable to parse config: $line\n";
+           }
+       } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
            $descr .= PVE::Tools::decode_text($2);
        } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
            $conf->{snapstate} = $1;
@@ -324,9 +450,10 @@ 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);
@@ -366,7 +493,7 @@ 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 {
@@ -401,10 +528,10 @@ sub lock_aquire {
                die "can't aquire lock - $!\n";
            }
 
-           $lock_handles->{$$}->{$filename}->{refcount}++;
-
            print STDERR " OK\n";
        }
+       
+       $lock_handles->{$$}->{$filename}->{refcount}++;
     };
 
     eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
@@ -578,8 +705,8 @@ sub vmstatus {
 
        $d->{mem} = 0;
        $d->{swap} = 0;
-       $d->{maxmem} = $conf->{memory}*1024*1024;
-       $d->{maxswap} = $conf->{swap}*1024*1024;
+       $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
+       $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
 
        $d->{uptime} = 0;
        $d->{cpu} = 0;
@@ -589,6 +716,8 @@ sub vmstatus {
 
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
+
+       $d->{template} = is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
@@ -630,6 +759,23 @@ my $parse_size = sub {
     return int($size);
 };
 
+my $format_size = sub {
+    my ($size) = @_;
+
+    $size = int($size);
+
+    my $kb = int($size/1024);
+    return $size if $kb*1024 != $size;
+
+    my $mb = int($kb/1024);
+    return "${kb}K" if $mb*1024 != $kb;
+
+    my $gb = int($mb/1024);
+    return "${mb}M" if $gb*1024 != $mb;
+
+    return "${gb}G";
+};
+
 sub parse_ct_mountpoint {
     my ($data) = @_;
 
@@ -640,9 +786,10 @@ sub parse_ct_mountpoint {
     foreach my $p (split (/,/, $data)) {
        next if $p =~ m/^\s*$/;
 
-       if ($p =~ m/^(volume|backup|size)=(.+)$/) {
+       if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
            my ($k, $v) = ($1, $2);
            return undef if defined($res->{$k});
+           $res->{$k} = $v;
        } else {
            if (!$res->{volume} && $p !~ m/=/) {
                $res->{volume} = $p;
@@ -652,7 +799,7 @@ sub parse_ct_mountpoint {
        }
     }
 
-    return undef if !$res->{volume};
+    return undef if !defined($res->{volume});
 
     return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
 
@@ -663,6 +810,26 @@ sub parse_ct_mountpoint {
     return $res;
 }
 
+sub print_ct_mountpoint {
+    my ($info, $nomp) = @_;
+
+    my $opts = '';
+
+    die "missing volume\n" if !$info->{volume};
+
+    foreach my $o (qw(backup)) {
+       $opts .= ",$o=$info->{$o}" if defined($info->{$o});
+    }
+
+    if ($info->{size}) {
+       $opts .= ",size=" . &$format_size($info->{size});
+    }
+
+    $opts .= ",mp=$info->{mp}" if !$nomp;
+
+    return "$info->{volume}$opts";
+}
+
 sub print_lxc_network {
     my $net = shift;
 
@@ -817,19 +984,33 @@ sub check_lock {
 sub update_lxc_config {
     my ($storage_cfg, $vmid, $conf) = @_;
 
+    my $dir = "/var/lib/lxc/$vmid";
+
+    if ($conf->{template}) {
+
+       unlink "$dir/config";
+
+       return;
+    }
+
     my $raw = '';
 
     die "missing 'arch' - internal error" if !$conf->{arch};
     $raw .= "lxc.arch = $conf->{arch}\n";
 
     my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
-    if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
+    if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
        $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
     } else {
        die "implement me";
     }
 
-    my $ttycount = $conf->{tty} // 4;
+    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";
 
     my $utsname = $conf->{hostname} || "CT$vmid";
@@ -853,35 +1034,37 @@ sub update_lxc_config {
     my $shares = $conf->{cpuunits} || 1024;
     $raw .= "lxc.cgroup.cpu.shares = $shares\n";
 
-    my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
-    my $volid = $rootinfo->{volume};
-    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
-
-    my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
-    if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
-       my $rootfs = PVE::Storage::path($storage_cfg, $volid);
-       $raw .= "lxc.rootfs = loop:$rootfs\n";
-    } elsif ($scfg->{type} eq 'zfspool') {
-       my $rootfs = PVE::Storage::path($storage_cfg, $volid);
-       $raw .= "lxc.rootfs = $rootfs\n";
-    }
+    my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
+    $mountpoint->{mp} = '/';
+    
+    my ($path, $use_loopdev) = mountpoint_mount_path($mountpoint, $storage_cfg);
+    $path = "loop:$path" if $use_loopdev;
+
+    $raw .= "lxc.rootfs = $path\n";
 
     my $netcount = 0;
     foreach my $k (keys %$conf) {
        next if $k !~ m/^net(\d+)$/;
        my $ind = $1;
-       my $d = PVE::LXC::parse_lxc_network($conf->{$k});
+       my $d = parse_lxc_network($conf->{$k});
        $netcount++;
        $raw .= "lxc.network.type = veth\n";
-       $raw .= "lxc.network.veth.pair = veth$vmid.$ind\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});
     }
 
-    $raw .= "lxc.network.type = empty\n" if !$netcount;
+    if (my $lxcconf = $conf->{lxc}) {
+       foreach my $entry (@$lxcconf) {
+           my ($k, $v) = @$entry;
+           $netcount++ if $k eq 'lxc.network.type';
+           $raw .= "$k = $v\n";
+       }
+    }
 
-    my $dir = "/var/lib/lxc/$vmid";
+    $raw .= "lxc.network.type = empty\n" if !$netcount;
+    
     File::Path::mkpath("$dir/rootfs");
 
     PVE::Tools::file_set_contents("$dir/config", $raw);
@@ -917,6 +1100,8 @@ sub update_pct_config {
 
     my @nohotplug;
 
+    my $new_disks = 0;
+
     my $rootdir;
     if ($running) {
        my $pid = find_lxc_pid($vmid);
@@ -932,7 +1117,8 @@ sub update_pct_config {
                write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
            } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
                delete $conf->{$opt};
-           } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain') {
+           } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
+                    $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
                delete $conf->{$opt};
                push @nohotplug, $opt;
                next if $running;
@@ -940,11 +1126,19 @@ sub update_pct_config {
                delete $conf->{$opt};
                next if !$running;
                my $netid = $1;
-               PVE::Network::veth_delete("veth${vmid}.$netid");
+               PVE::Network::veth_delete("veth${vmid}i$netid");
+           } elsif ($opt eq 'protection') {
+               delete $conf->{$opt};
+           } elsif ($opt =~ m/^mp(\d+)$/) {
+               delete $conf->{$opt};
+               push @nohotplug, $opt;
+               next if $running;
+           } elsif ($opt eq 'rootfs') {
+               die "implement me"
            } else {
                die "implement me"
            }
-           PVE::LXC::write_config($vmid, $conf) if $running;
+           write_config($vmid, $conf) if $running;
        }
     }
 
@@ -965,7 +1159,7 @@ sub update_pct_config {
        $conf->{memory} = $wanted_memory;
        $conf->{swap} = $wanted_swap;
 
-       PVE::LXC::write_config($vmid, $conf) if $running;
+       write_config($vmid, $conf) if $running;
     }
 
     foreach my $opt (keys %$param) {
@@ -976,6 +1170,10 @@ sub update_pct_config {
            $conf->{$opt} = $value ? 1 : 0;
        } elsif ($opt eq 'startup') {
            $conf->{$opt} = $value;
+       } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
+           $conf->{$opt} = $value;
+           push @nohotplug, $opt;
+           next if $running;
        } elsif ($opt eq 'nameserver') {
            my $list = verify_nameserver_list($value);
            $conf->{$opt} = $list;
@@ -997,21 +1195,69 @@ sub update_pct_config {
            $conf->{$opt} = PVE::Tools::encode_text($value);
        } elsif ($opt =~ m/^net(\d+)$/) {
            my $netid = $1;
-           my $net = PVE::LXC::parse_lxc_network($value);
+           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+)$/) {
+           $conf->{$opt} = $value;
+           $new_disks = 1;
+           push @nohotplug, $opt;
+           next;
+        } elsif ($opt eq 'rootfs') {
+           die "implement me: $opt";
        } else {
            die "implement me: $opt";
        }
-       PVE::LXC::write_config($vmid, $conf) if $running;
+       write_config($vmid, $conf) if $running;
     }
 
     if ($running && scalar(@nohotplug)) {
        die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
     }
+
+    if ($new_disks) {
+       my $storage_cfg = PVE::Storage::config();
+       create_disks($storage_cfg, $vmid, $conf, $conf);
+    }
+}
+
+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 {
@@ -1020,7 +1266,7 @@ sub get_primary_ips {
     # return data from net0
 
     return undef if !defined($conf->{net0});
-    my $net = PVE::LXC::parse_lxc_network($conf->{net0});
+    my $net = parse_lxc_network($conf->{net0});
 
     my $ipv4 = $net->{ip};
     if ($ipv4) {
@@ -1042,14 +1288,16 @@ sub get_primary_ips {
     return ($ipv4, $ipv6);
 }
 
+
 sub destroy_lxc_container {
     my ($storage_cfg, $vmid, $conf) = @_;
 
-    my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
-    if (defined($rootinfo->{volume})) {
-       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
-       PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
-    }
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+       my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
+       PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
+    });
+
     rmdir "/var/lib/lxc/$vmid/rootfs";
     unlink "/var/lib/lxc/$vmid/config";
     rmdir "/var/lib/lxc/$vmid";
@@ -1059,6 +1307,19 @@ sub destroy_lxc_container {
     #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 {
     my ($a, $b) = @_;
 
@@ -1082,49 +1343,57 @@ 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});
+               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};
 
@@ -1139,28 +1408,21 @@ 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 $nscmd = sub {
@@ -1249,7 +1511,8 @@ 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);
     };
 
@@ -1290,6 +1553,9 @@ my $snapshot_prepare = 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';
@@ -1309,7 +1575,7 @@ my $snapshot_prepare = sub {
        $snap->{'description'} = $comment if $comment;
        $conf->{snapshots}->{$snapname} = $snap;
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
@@ -1338,7 +1604,7 @@ my $snapshot_commit = sub {
        delete $conf->{lock};
        $conf->{parent} = $snapname;
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10 ,$updatefn);
@@ -1347,11 +1613,19 @@ my $snapshot_commit = sub {
 sub has_feature {
     my ($feature, $conf, $storecfg, $snapname) = @_;
     
-    #Fixme add other drives if necessary.
     my $err;
 
-    my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
-    $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
+    foreach_mountpoint($conf, sub {
+       my ($ms, $mountpoint) = @_;
+
+       return if $err; # skip further test
+       
+       $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;
 }
@@ -1371,7 +1645,7 @@ sub snapshot_create {
        };
 
        my $storecfg = PVE::Storage::config();
-       my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
+       my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
        my $volid = $rootinfo->{volume};
 
        $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
@@ -1399,6 +1673,9 @@ sub snapshot_delete {
 
        $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);
@@ -1407,7 +1684,7 @@ sub snapshot_delete {
 
        $snap->{snapstate} = 'delete';
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
@@ -1428,11 +1705,11 @@ sub snapshot_delete {
 
        delete $conf->{snapshots}->{$snapname};
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
-    my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
+    my $rootinfo = parse_ct_mountpoint($rootfs);
     my $volid = $rootinfo->{volume};
 
     eval {
@@ -1455,12 +1732,14 @@ sub snapshot_rollback {
 
     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 = PVE::LXC::parse_ct_mountpoint($rootfs);
+    my $rootinfo = parse_ct_mountpoint($rootfs);
     my $volid = $rootinfo->{volume};
 
     PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
@@ -1490,12 +1769,12 @@ sub snapshot_rollback {
        delete $conf->{snapname};
        $conf->{parent} = $snapname;
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     my $unlockfn = sub {
        delete $conf->{lock};
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
@@ -1505,4 +1784,462 @@ sub snapshot_rollback {
     lock_container($vmid, 5, $unlockfn);
 }
 
+sub template_create {
+    my ($vmid, $conf) = @_;
+
+    my $storecfg = PVE::Storage::config();
+
+    my $rootinfo = parse_ct_mountpoint($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;
+}
+
+# The container might have *different* symlinks than the host. realpath/abs_path
+# use the actual filesystem to resolve links.
+sub sanitize_mountpoint {
+    my ($mp) = @_;
+    $mp = '/' . $mp; # we always start with a slash
+    $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
+    $mp =~ s@/\./@@g; # collapse /./
+    $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
+    $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
+    $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
+    return $mp;
+}
+
+sub foreach_mountpoint_full {
+    my ($conf, $reverse, $func) = @_;
+
+    foreach my $key (mountpoint_names($reverse)) {
+       my $value = $conf->{$key};
+       next if !defined($value);
+       my $mountpoint = parse_ct_mountpoint($value);
+
+       # just to be sure: rootfs is /
+       my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
+       $mountpoint->{mp} = sanitize_mountpoint($path);
+
+       $path = $mountpoint->{volume};
+       $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
+
+       &$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) = @_;
+
+           my $volid = $mountpoint->{volume};
+           my $mount = $mountpoint->{mp};
+
+           return if !$volid || !$mount;
+
+           my $image_path = PVE::Storage::path($storage_cfg, $volid);
+           my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+               PVE::Storage::parse_volname($storage_cfg, $volid);
+
+           die "unable to mount base volume - internal error" if $isBase;
+
+           mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+        });
+    };
+    if (my $err = $@) {
+       warn "mounting container failed - $err";
+       umount_all($vmid, $storage_cfg, $conf, 1);
+    }
+
+    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";
+    }
+};
+
+# 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};
+    
+    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);
+
+    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);
+
+       if ($format eq 'subvol') {
+           if ($mount_path) {
+               if ($snapname) {
+                   if ($scfg->{type} eq 'zfspool') {
+                       my $path_arg = $path;
+                       $path_arg =~ s!^/+!!;
+                       PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+                   } else {
+                       die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
+                   }
+               } else {
+                   PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
+               }
+           }
+           return wantarray ? ($path, 0) : $path;
+       } elsif ($format eq 'raw') {
+           my $use_loopdev = 0;
+           my @extra_opts;
+           if ($scfg->{path}) {
+               push @extra_opts, '-o', 'loop';
+               $use_loopdev = 1;
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
+               # do nothing
+           } else {
+               die "unsupported storage type '$scfg->{type}'\n";
+           }
+           if ($mount_path) {
+               if ($isBase || defined($snapname)) {
+                   PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
+               } else {
+                   PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+               }
+           }
+           return wantarray ? ($path, $use_loopdev) : $path;
+       } else {
+           die "unsupported image format '$format'\n";
+       }
+    } elsif ($volid =~ m|^/dev/.+|) {
+       PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
+       return wantarray ? ($volid, 0) : $volid;
+    } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
+       &$check_mount_path($volid);
+       PVE::Tools::run_command(['mount', '-o', 'bind', $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 || $volid =~ m|^/|;
+
+        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+        return if !$sid;
+
+        push @$vollist, $volid;
+    });
+
+    return $vollist;
+}
+
+sub mkfs {
+    my ($dev) = @_;
+
+    PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
+}
+
+sub format_disk {
+    my ($storage_cfg, $volid) = @_;
+
+    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);
+}
+
+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 {
+       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);
+
+           return if !$storage;
+
+           if ($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);
+                   } else {
+                       $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                          undef, 0);
+                   }
+               } elsif ($scfg->{type} eq 'zfspool') {
+
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+                                                      undef, $size_kb);
+               } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
+
+                   $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+                   format_disk($storecfg, $volid);
+
+               } 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);
+               } else {
+                   die "unable to create containers on storage type '$scfg->{type}'\n";
+               }
+               push @$vollist, $volid;
+                my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
+               $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
+           } else {
+               # use specified/existing volid
+           }
+       });
+    };
+    # 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);
+}
+
 1;