+ &$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 [];