]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
format_disk: call activate_volume()
[pve-container.git] / src / PVE / LXC.pm
index a09f5fabbe3a9469b03b09bc243cf5b4c5973098..f34447023664c355cfe04ac530e05167ab62d4fd 100644 (file)
@@ -12,8 +12,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 +48,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 +72,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 +88,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,
@@ -149,6 +169,19 @@ 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 = {
@@ -227,6 +260,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) = @_;
 
@@ -341,7 +385,7 @@ sub parse_pct_config {
            next;
        }
 
-       if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\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\./) {
@@ -402,9 +446,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);
@@ -444,7 +489,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 {
@@ -479,10 +524,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); };
@@ -656,8 +701,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;
@@ -667,6 +712,8 @@ sub vmstatus {
 
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
+
+       $d->{template} = is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
@@ -708,6 +755,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) = @_;
 
@@ -718,9 +782,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;
@@ -730,7 +795,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)$/;
 
@@ -741,6 +806,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;
 
@@ -895,19 +980,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";
@@ -931,24 +1030,20 @@ 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";
-    } elsif ($scfg->{type} eq 'drbd') {
-       my $rootdev = PVE::Storage::path($storage_cfg, $volid);
-       $raw .= "lxc.rootfs = $rootdev\n";
-    } else {
-       die "unsupported storage type '$scfg->{type}'\n";
+    my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
+    $mountpoint->{mp} = '/';
+    my $volid = $mountpoint->{volume};
+    my $path = mountpoint_mount_path($mountpoint, $storage_cfg);
+    
+    my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+    if ($storage) {
+       my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
+       $path = "loop:$path" if $scfg->{path};
     }
 
+    $raw .= "lxc.rootfs = $path\n";
+
     my $netcount = 0;
     foreach my $k (keys %$conf) {
        next if $k !~ m/^net(\d+)$/;
@@ -972,7 +1067,6 @@ sub update_lxc_config {
 
     $raw .= "lxc.network.type = empty\n" if !$netcount;
     
-    my $dir = "/var/lib/lxc/$vmid";
     File::Path::mkpath("$dir/rootfs");
 
     PVE::Tools::file_set_contents("$dir/config", $raw);
@@ -1008,6 +1102,8 @@ sub update_pct_config {
 
     my @nohotplug;
 
+    my $new_disks = [];
+
     my $rootdir;
     if ($running) {
        my $pid = find_lxc_pid($vmid);
@@ -1023,7 +1119,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;
@@ -1032,10 +1129,18 @@ sub update_pct_config {
                next if !$running;
                my $netid = $1;
                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;
        }
     }
 
@@ -1056,7 +1161,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) {
@@ -1067,7 +1172,7 @@ sub update_pct_config {
            $conf->{$opt} = $value ? 1 : 0;
        } elsif ($opt eq 'startup') {
            $conf->{$opt} = $value;
-       } elsif ($opt eq 'tty') {
+       } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
            $conf->{$opt} = $value;
            push @nohotplug, $opt;
            next if $running;
@@ -1098,15 +1203,65 @@ sub update_pct_config {
            } 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;
+           push @$new_disks, $opt;
+           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);
+       mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
+       umount_all($vmid, $storage_cfg, $conf, 0);
+    }
+}
+
+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 {
@@ -1137,14 +1292,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";
@@ -1154,6 +1311,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) = @_;
 
@@ -1193,7 +1363,7 @@ sub update_net {
 
            PVE::Network::veth_delete($veth);
            delete $conf->{$opt};
-           PVE::LXC::write_config($vmid, $conf);
+           write_config($vmid, $conf);
 
            hotplug_net($vmid, $conf, $opt, $newnet, $netid);
 
@@ -1207,7 +1377,7 @@ sub update_net {
                        delete $oldnet->{$_};
                    }
                    $conf->{$opt} = print_lxc_network($oldnet);
-                   PVE::LXC::write_config($vmid, $conf);
+                   write_config($vmid, $conf);
                }
 
                PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
@@ -1215,7 +1385,7 @@ sub update_net {
                    $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
                }
                $conf->{$opt} = print_lxc_network($oldnet);
-               PVE::LXC::write_config($vmid, $conf);
+               write_config($vmid, $conf);
        }
     } else {
        hotplug_net($vmid, $conf, $opt, $newnet, $netid);
@@ -1248,13 +1418,13 @@ sub hotplug_net {
     }
     $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 = parse_lxc_network($conf->{$opt});
     my $deleted = [];
@@ -1346,7 +1516,7 @@ sub update_ipconfig {
            }
        }
        $conf->{$opt} = print_lxc_network($optdata);
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
        $lxc_setup->setup_network($conf);
     };
 
@@ -1387,6 +1557,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';
@@ -1406,7 +1579,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);
@@ -1435,7 +1608,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);
@@ -1444,11 +1617,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;
 }
@@ -1468,7 +1649,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";
@@ -1496,6 +1677,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);
@@ -1504,7 +1688,7 @@ sub snapshot_delete {
 
        $snap->{snapstate} = 'delete';
 
-       PVE::LXC::write_config($vmid, $conf);
+       write_config($vmid, $conf);
     };
 
     lock_container($vmid, 10, $updatefn);
@@ -1525,11 +1709,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 {
@@ -1552,12 +1736,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);
@@ -1587,12 +1773,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);
@@ -1602,4 +1788,427 @@ 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;
+}
+
+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);
+       $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
+       &$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 'disk') {
+           $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, $mkdirs) = @_;
+
+    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;
+
+           File::Path::make_path "$rootdir/$mount" if $mkdirs;
+           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);
+}
+
+# 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;
+       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);
+       return $path if !$mount_path;
+
+       my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+           PVE::Storage::parse_volname($storage_cfg, $volid);
+
+       if ($format eq 'subvol') {
+           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 $path;
+       } elsif ($format eq 'raw') {
+           my @extra_opts;
+           if ($scfg->{path}) {
+               push @extra_opts, '-o', 'loop';
+           } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
+               # do nothing
+           } else {
+               die "unsupported storage type '$scfg->{type}'\n";
+           }
+           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 $path;
+       } else {
+           die "unsupported image format '$format'\n";
+       }
+    } elsif ($volid =~ m|^/dev/.+|) {
+       PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
+       return $volid;
+    } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
+       PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
+       return $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') {
+
+                   $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;