12 use Fcntl
qw(O_RDONLY);
14 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
18 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
21 use PVE
::AccessControl
;
23 use Time
::HiRes qw
(gettimeofday
);
27 my $nodename = PVE
::INotify
::nodename
();
29 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
31 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
33 '--xattrs-include=user.*',
34 '--xattrs-include=security.capability',
35 '--warning=no-xattr-write' ];
37 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
43 format
=> 'pve-lxc-mp-string',
44 format_description
=> 'volume',
45 description
=> 'Volume, device or directory to mount into the container.',
49 format_description
=> '[1|0]',
50 description
=> 'Whether to include the mountpoint in backups.',
55 format
=> 'disk-size',
56 format_description
=> 'DiskSize',
57 description
=> 'Volume size (read only value).',
62 format_description
=> 'acl',
63 description
=> 'Explicitly enable or disable ACL support.',
68 format_description
=> 'ro',
69 description
=> 'Read-only mountpoint (not supported with bind mounts)',
74 format_description
=> '[0|1]',
75 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
80 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
81 type
=> 'string', format
=> $rootfs_desc,
82 description
=> "Use volume as container root.",
86 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
87 description
=> "The name of the snapshot.",
88 type
=> 'string', format
=> 'pve-configid',
96 description
=> "Lock/unlock the VM.",
97 enum
=> [qw(migrate backup snapshot rollback)],
102 description
=> "Specifies whether a VM will be started during system bootup.",
105 startup
=> get_standard_option
('pve-startup-order'),
109 description
=> "Enable/disable Template.",
115 enum
=> ['amd64', 'i386'],
116 description
=> "OS architecture type.",
122 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'],
123 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
128 description
=> "Attach a console device (/dev/console) to the container.",
134 description
=> "Specify the number of tty available to the container",
142 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
150 description
=> "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
158 description
=> "Amount of RAM for the VM in MB.",
165 description
=> "Amount of SWAP for the VM in MB.",
171 description
=> "Set a host name for the container.",
172 type
=> 'string', format
=> 'dns-name',
178 description
=> "Container description. Only used on the configuration web interface.",
182 type
=> 'string', format
=> 'dns-name-list',
183 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
187 type
=> 'string', format
=> 'address-list',
188 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
190 rootfs
=> get_standard_option
('pve-ct-rootfs'),
193 type
=> 'string', format
=> 'pve-configid',
195 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
199 description
=> "Timestamp for snapshots.",
205 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).",
207 enum
=> ['shell', 'console', 'tty'],
213 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
219 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
224 my $valid_lxc_conf_keys = {
228 'lxc.haltsignal' => 1,
229 'lxc.rebootsignal' => 1,
230 'lxc.stopsignal' => 1,
232 'lxc.network.type' => 1,
233 'lxc.network.flags' => 1,
234 'lxc.network.link' => 1,
235 'lxc.network.mtu' => 1,
236 'lxc.network.name' => 1,
237 'lxc.network.hwaddr' => 1,
238 'lxc.network.ipv4' => 1,
239 'lxc.network.ipv4.gateway' => 1,
240 'lxc.network.ipv6' => 1,
241 'lxc.network.ipv6.gateway' => 1,
242 'lxc.network.script.up' => 1,
243 'lxc.network.script.down' => 1,
245 'lxc.console.logfile' => 1,
248 'lxc.devttydir' => 1,
249 'lxc.hook.autodev' => 1,
253 'lxc.mount.entry' => 1,
254 'lxc.mount.auto' => 1,
255 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
256 'lxc.rootfs.mount' => 1,
257 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
258 ', please use mountpoint options in the "rootfs" key',
262 'lxc.aa_profile' => 1,
263 'lxc.aa_allow_incomplete' => 1,
264 'lxc.se_context' => 1,
267 'lxc.hook.pre-start' => 1,
268 'lxc.hook.pre-mount' => 1,
269 'lxc.hook.mount' => 1,
270 'lxc.hook.start' => 1,
271 'lxc.hook.stop' => 1,
272 'lxc.hook.post-stop' => 1,
273 'lxc.hook.clone' => 1,
274 'lxc.hook.destroy' => 1,
277 'lxc.start.auto' => 1,
278 'lxc.start.delay' => 1,
279 'lxc.start.order' => 1,
281 'lxc.environment' => 1,
288 description
=> "Network interface type.",
293 format_description
=> 'String',
294 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
295 pattern
=> '[-_.\w\d]+',
299 format_description
=> 'vmbr<Number>',
300 description
=> 'Bridge to attach the network device to.',
301 pattern
=> '[-_.\w\d]+',
306 format_description
=> 'MAC',
307 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
308 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
313 format_description
=> 'Number',
314 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
315 minimum
=> 64, # minimum ethernet frame is 64 bytes
320 format
=> 'pve-ipv4-config',
321 format_description
=> 'IPv4Format/CIDR',
322 description
=> 'IPv4 address in CIDR format.',
328 format_description
=> 'GatewayIPv4',
329 description
=> 'Default gateway for IPv4 traffic.',
334 format
=> 'pve-ipv6-config',
335 format_description
=> 'IPv6Format/CIDR',
336 description
=> 'IPv6 address in CIDR format.',
342 format_description
=> 'GatewayIPv6',
343 description
=> 'Default gateway for IPv6 traffic.',
348 format_description
=> '[1|0]',
349 description
=> "Controls whether this interface's firewall rules should be used.",
354 format_description
=> 'VlanNo',
357 description
=> "VLAN tag for this interface.",
362 pattern
=> qr/\d+(?:;\d+)*/,
363 format_description
=> 'vlanid[;vlanid...]',
364 description
=> "VLAN ids to pass through the interface",
368 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
370 my $MAX_LXC_NETWORKS = 10;
371 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
372 $confdesc->{"net$i"} = {
374 type
=> 'string', format
=> $netconf_desc,
375 description
=> "Specifies network interfaces for the container.",
379 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
380 sub verify_lxc_mp_string
{
381 my ($mp, $noerr) = @_;
385 # /. or /.. at the end
386 # ../ at the beginning
388 if($mp =~ m
@/\.\
.?
/@ ||
391 return undef if $noerr;
392 die "$mp contains illegal character sequences\n";
401 format
=> 'pve-lxc-mp-string',
402 format_description
=> 'Path',
403 description
=> 'Path to the mountpoint as seen from inside the container.',
406 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
410 type
=> 'string', format
=> 'pve-volume-id',
411 description
=> "Reference to unused volumes.",
414 my $MAX_MOUNT_POINTS = 10;
415 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
416 $confdesc->{"mp$i"} = {
418 type
=> 'string', format
=> $mp_desc,
419 description
=> "Use volume as container mount point (experimental feature).",
424 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
425 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
426 $confdesc->{"unused$i"} = $unuseddesc;
429 sub write_pct_config
{
430 my ($filename, $conf) = @_;
432 delete $conf->{snapstate
}; # just to be sure
434 my $generate_raw_config = sub {
439 # add description as comment to top of file
440 my $descr = $conf->{description
} || '';
441 foreach my $cl (split(/\n/, $descr)) {
442 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
445 foreach my $key (sort keys %$conf) {
446 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
447 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
448 my $value = $conf->{$key};
449 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
450 $raw .= "$key: $value\n";
453 if (my $lxcconf = $conf->{lxc
}) {
454 foreach my $entry (@$lxcconf) {
455 my ($k, $v) = @$entry;
463 my $raw = &$generate_raw_config($conf);
465 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
466 $raw .= "\n[$snapname]\n";
467 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
474 my ($key, $value) = @_;
476 die "unknown setting '$key'\n" if !$confdesc->{$key};
478 my $type = $confdesc->{$key}->{type
};
480 if (!defined($value)) {
481 die "got undefined value\n";
484 if ($value =~ m/[\n\r]/) {
485 die "property contains a line feed\n";
488 if ($type eq 'boolean') {
489 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
490 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
491 die "type check ('boolean') failed - got '$value'\n";
492 } elsif ($type eq 'integer') {
493 return int($1) if $value =~ m/^(\d+)$/;
494 die "type check ('integer') failed - got '$value'\n";
495 } elsif ($type eq 'number') {
496 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
497 die "type check ('number') failed - got '$value'\n";
498 } elsif ($type eq 'string') {
499 if (my $fmt = $confdesc->{$key}->{format
}) {
500 PVE
::JSONSchema
::check_format
($fmt, $value);
509 sub parse_pct_config
{
510 my ($filename, $raw) = @_;
512 return undef if !defined($raw);
515 digest
=> Digest
::SHA
::sha1_hex
($raw),
519 $filename =~ m
|/lxc/(\d
+).conf
$|
520 || die "got strange filename '$filename'";
528 my @lines = split(/\n/, $raw);
529 foreach my $line (@lines) {
530 next if $line =~ m/^\s*$/;
532 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
534 $conf->{description
} = $descr if $descr;
536 $conf = $res->{snapshots
}->{$section} = {};
540 if ($line =~ m/^\#(.*)\s*$/) {
541 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
545 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
548 my $validity = $valid_lxc_conf_keys->{$key} || 0;
549 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
550 push @{$conf->{lxc
}}, [$key, $value];
551 } elsif (my $errmsg = $validity) {
552 warn "vm $vmid - $key: $errmsg\n";
554 warn "vm $vmid - unable to parse config: $line\n";
556 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
557 $descr .= PVE
::Tools
::decode_text
($2);
558 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
559 $conf->{snapstate
} = $1;
560 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
563 eval { $value = check_type
($key, $value); };
564 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
565 $conf->{$key} = $value;
567 warn "vm $vmid - unable to parse config: $line\n";
571 $conf->{description
} = $descr if $descr;
573 delete $res->{snapstate
}; # just to be sure
579 my $vmlist = PVE
::Cluster
::get_vmlist
();
581 return $res if !$vmlist || !$vmlist->{ids
};
582 my $ids = $vmlist->{ids
};
584 foreach my $vmid (keys %$ids) {
585 next if !$vmid; # skip CT0
586 my $d = $ids->{$vmid};
587 next if !$d->{node
} || $d->{node
} ne $nodename;
588 next if !$d->{type
} || $d->{type
} ne 'lxc';
589 $res->{$vmid}->{type
} = 'lxc';
594 sub cfs_config_path
{
595 my ($vmid, $node) = @_;
597 $node = $nodename if !$node;
598 return "nodes/$node/lxc/$vmid.conf";
602 my ($vmid, $node) = @_;
604 my $cfspath = cfs_config_path
($vmid, $node);
605 return "/etc/pve/$cfspath";
609 my ($vmid, $node) = @_;
611 $node = $nodename if !$node;
612 my $cfspath = cfs_config_path
($vmid, $node);
614 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
615 die "container $vmid does not exist\n" if !defined($conf);
621 my ($vmid, $conf) = @_;
623 my $dir = "/etc/pve/nodes/$nodename/lxc";
626 write_config
($vmid, $conf);
632 unlink config_file
($vmid, $nodename);
636 my ($vmid, $conf) = @_;
638 my $cfspath = cfs_config_path
($vmid);
640 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
643 # flock: we use one file handle per process, so lock file
644 # can be called multiple times and will succeed for the same process.
646 my $lock_handles = {};
647 my $lockdir = "/run/lock/lxc";
649 sub config_file_lock
{
652 return "$lockdir/pve-config-${vmid}.lock";
655 sub lock_config_full
{
656 my ($vmid, $timeout, $code, @param) = @_;
658 my $filename = config_file_lock
($vmid);
660 mkdir $lockdir if !-d
$lockdir;
662 my $res = lock_file
($filename, $timeout, $code, @param);
669 sub lock_config_mode
{
670 my ($vmid, $timeout, $shared, $code, @param) = @_;
672 my $filename = config_file_lock
($vmid);
674 mkdir $lockdir if !-d
$lockdir;
676 my $res = lock_file_full
($filename, $timeout, $shared, $code, @param);
684 my ($vmid, $code, @param) = @_;
686 return lock_config_full
($vmid, 10, $code, @param);
692 return defined($confdesc->{$name});
695 # add JSON properties for create and set function
696 sub json_config_properties
{
699 foreach my $opt (keys %$confdesc) {
700 next if $opt eq 'parent' || $opt eq 'snaptime';
701 next if $prop->{$opt};
702 $prop->{$opt} = $confdesc->{$opt};
708 # container status helpers
710 sub list_active_containers
{
712 my $filename = "/proc/net/unix";
714 # similar test is used by lcxcontainers.c: list_active_containers
717 my $fh = IO
::File-
>new ($filename, "r");
720 while (defined(my $line = <$fh>)) {
721 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
723 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
734 # warning: this is slow
738 my $active_hash = list_active_containers
();
740 return 1 if defined($active_hash->{$vmid});
745 sub get_container_disk_usage
{
746 my ($vmid, $pid) = @_;
748 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
751 my $last_proc_vmid_stat;
753 my $parse_cpuacct_stat = sub {
756 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
760 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
773 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
775 my $active_hash = list_active_containers
();
777 my $cpucount = $cpuinfo->{cpus
} || 1;
779 my $cdtime = gettimeofday
;
781 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
783 foreach my $vmid (keys %$list) {
784 my $d = $list->{$vmid};
786 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
787 warn $@ if $@; # ignore errors (consider them stopped)
789 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
791 my $cfspath = cfs_config_path
($vmid);
792 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
794 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
795 $d->{name
} =~ s/[\s]//g;
797 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
800 my $res = get_container_disk_usage
($vmid, $d->{pid
});
801 $d->{disk
} = $res->{used
};
802 $d->{maxdisk
} = $res->{total
};
805 # use 4GB by default ??
806 if (my $rootfs = $conf->{rootfs
}) {
807 my $rootinfo = parse_ct_rootfs
($rootfs);
808 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
810 $d->{maxdisk
} = 4*1024*1024*1024;
816 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
817 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
828 $d->{template
} = is_template
($conf);
831 foreach my $vmid (keys %$list) {
832 my $d = $list->{$vmid};
835 next if !$pid; # skip stopped CTs
837 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
838 $d->{uptime
} = time - $ctime; # the method lxcfs uses
840 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
841 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
843 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
844 my @bytes = split(/\n/, $blkio_bytes);
845 foreach my $byte (@bytes) {
846 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
847 $d->{diskread
} = $2 if $key eq 'Read';
848 $d->{diskwrite
} = $2 if $key eq 'Write';
852 my $pstat = &$parse_cpuacct_stat($vmid);
854 my $used = $pstat->{utime} + $pstat->{stime
};
856 my $old = $last_proc_vmid_stat->{$vmid};
858 $last_proc_vmid_stat->{$vmid} = {
866 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
869 my $dutime = $used - $old->{used
};
871 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
872 $last_proc_vmid_stat->{$vmid} = {
878 $d->{cpu
} = $old->{cpu
};
882 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
884 foreach my $dev (keys %$netdev) {
885 next if $dev !~ m/^veth([1-9]\d*)i/;
887 my $d = $list->{$vmid};
891 $d->{netout
} += $netdev->{$dev}->{receive
};
892 $d->{netin
} += $netdev->{$dev}->{transmit
};
899 sub classify_mountpoint
{
902 return 'device' if $vol =~ m!^/dev/!;
908 my $parse_ct_mountpoint_full = sub {
909 my ($desc, $data, $noerr) = @_;
914 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
916 return undef if $noerr;
920 if (defined(my $size = $res->{size
})) {
921 $size = PVE
::JSONSchema
::parse_size
($size);
922 if (!defined($size)) {
923 return undef if $noerr;
924 die "invalid size: $size\n";
926 $res->{size
} = $size;
929 $res->{type
} = classify_mountpoint
($res->{volume
});
934 sub parse_ct_rootfs
{
935 my ($data, $noerr) = @_;
937 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
939 $res->{mp
} = '/' if defined($res);
944 sub parse_ct_mountpoint
{
945 my ($data, $noerr) = @_;
947 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
950 sub print_ct_mountpoint
{
951 my ($info, $nomp) = @_;
952 my $skip = [ 'type' ];
953 push @$skip, 'mp' if $nomp;
954 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
957 sub print_lxc_network
{
959 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
962 sub parse_lxc_network
{
967 return $res if !$data;
969 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
971 $res->{type
} = 'veth';
972 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
977 sub read_cgroup_value
{
978 my ($group, $vmid, $name, $full) = @_;
980 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
982 return PVE
::Tools
::file_get_contents
($path) if $full;
984 return PVE
::Tools
::file_read_firstline
($path);
987 sub write_cgroup_value
{
988 my ($group, $vmid, $name, $value) = @_;
990 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
991 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
995 sub find_lxc_console_pids
{
999 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1002 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1003 return if !$cmdline;
1005 my @args = split(/\0/, $cmdline);
1007 # search for lxc-console -n <vmid>
1008 return if scalar(@args) != 3;
1009 return if $args[1] ne '-n';
1010 return if $args[2] !~ m/^\d+$/;
1011 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1013 my $vmid = $args[2];
1015 push @{$res->{$vmid}}, $pid;
1027 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1029 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1031 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1036 # Note: we cannot use Net:IP, because that only allows strict
1038 sub parse_ipv4_cidr
{
1039 my ($cidr, $noerr) = @_;
1041 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1042 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1045 return undef if $noerr;
1047 die "unable to parse ipv4 address/mask\n";
1053 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1056 sub check_protection
{
1057 my ($vm_conf, $err_msg) = @_;
1059 if ($vm_conf->{protection
}) {
1060 die "$err_msg - protection mode enabled\n";
1064 sub update_lxc_config
{
1065 my ($storage_cfg, $vmid, $conf) = @_;
1067 my $dir = "/var/lib/lxc/$vmid";
1069 if ($conf->{template
}) {
1071 unlink "$dir/config";
1078 die "missing 'arch' - internal error" if !$conf->{arch
};
1079 $raw .= "lxc.arch = $conf->{arch}\n";
1081 my $unprivileged = $conf->{unprivileged
};
1082 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1084 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1085 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1086 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1087 if ($unprivileged || $custom_idmap) {
1088 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1091 die "implement me (ostype $ostype)";
1094 # WARNING: DO NOT REMOVE this without making sure that loop device nodes
1095 # cannot be exposed to the container with r/w access (cgroup perms).
1096 # When this is enabled mounts will still remain in the monitor's namespace
1097 # after the container unmounted them and thus will not detach from their
1098 # files while the container is running!
1099 $raw .= "lxc.monitor.unshare = 1\n";
1101 # Should we read them from /etc/subuid?
1102 if ($unprivileged && !$custom_idmap) {
1103 $raw .= "lxc.id_map = u 0 100000 65536\n";
1104 $raw .= "lxc.id_map = g 0 100000 65536\n";
1107 if (!has_dev_console
($conf)) {
1108 $raw .= "lxc.console = none\n";
1109 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1112 my $ttycount = get_tty_count
($conf);
1113 $raw .= "lxc.tty = $ttycount\n";
1115 # some init scripts expect a linux terminal (turnkey).
1116 $raw .= "lxc.environment = TERM=linux\n";
1118 my $utsname = $conf->{hostname
} || "CT$vmid";
1119 $raw .= "lxc.utsname = $utsname\n";
1121 my $memory = $conf->{memory
} || 512;
1122 my $swap = $conf->{swap
} // 0;
1124 my $lxcmem = int($memory*1024*1024);
1125 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1127 my $lxcswap = int(($memory + $swap)*1024*1024);
1128 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1130 if (my $cpulimit = $conf->{cpulimit
}) {
1131 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1132 my $value = int(100000*$cpulimit);
1133 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1136 my $shares = $conf->{cpuunits
} || 1024;
1137 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1139 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1141 $raw .= "lxc.rootfs = $dir/rootfs\n";
1144 foreach my $k (keys %$conf) {
1145 next if $k !~ m/^net(\d+)$/;
1147 my $d = parse_lxc_network
($conf->{$k});
1149 $raw .= "lxc.network.type = veth\n";
1150 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1151 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1152 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1153 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1156 if (my $lxcconf = $conf->{lxc
}) {
1157 foreach my $entry (@$lxcconf) {
1158 my ($k, $v) = @$entry;
1159 $netcount++ if $k eq 'lxc.network.type';
1160 $raw .= "$k = $v\n";
1164 $raw .= "lxc.network.type = empty\n" if !$netcount;
1166 File
::Path
::mkpath
("$dir/rootfs");
1168 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1171 # verify and cleanup nameserver list (replace \0 with ' ')
1172 sub verify_nameserver_list
{
1173 my ($nameserver_list) = @_;
1176 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1177 PVE
::JSONSchema
::pve_verify_ip
($server);
1178 push @list, $server;
1181 return join(' ', @list);
1184 sub verify_searchdomain_list
{
1185 my ($searchdomain_list) = @_;
1188 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1189 # todo: should we add checks for valid dns domains?
1190 push @list, $server;
1193 return join(' ', @list);
1196 sub add_unused_volume
{
1197 my ($config, $volid) = @_;
1200 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1201 my $test = "unused$ind";
1202 if (my $vid = $config->{$test}) {
1203 return if $vid eq $volid; # do not add duplicates
1209 die "Too many unused volumes - please delete them first.\n" if !$key;
1211 $config->{$key} = $volid;
1216 sub update_pct_config
{
1217 my ($vmid, $conf, $running, $param, $delete) = @_;
1222 my @deleted_volumes;
1226 my $pid = find_lxc_pid
($vmid);
1227 $rootdir = "/proc/$pid/root";
1230 my $hotplug_error = sub {
1232 push @nohotplug, @_;
1239 if (defined($delete)) {
1240 foreach my $opt (@$delete) {
1241 if (!exists($conf->{$opt})) {
1242 warn "no such option: $opt\n";
1246 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1247 die "unable to delete required option '$opt'\n";
1248 } elsif ($opt eq 'swap') {
1249 delete $conf->{$opt};
1250 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1251 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1252 delete $conf->{$opt};
1253 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1254 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1255 next if $hotplug_error->($opt);
1256 delete $conf->{$opt};
1257 } elsif ($opt =~ m/^net(\d)$/) {
1258 delete $conf->{$opt};
1261 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1262 } elsif ($opt eq 'protection') {
1263 delete $conf->{$opt};
1264 } elsif ($opt =~ m/^unused(\d+)$/) {
1265 next if $hotplug_error->($opt);
1266 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1267 push @deleted_volumes, $conf->{$opt};
1268 delete $conf->{$opt};
1269 } elsif ($opt =~ m/^mp(\d+)$/) {
1270 next if $hotplug_error->($opt);
1271 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1272 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1273 if ($mountpoint->{type
} eq 'volume') {
1274 add_unused_volume
($conf, $mountpoint->{volume
})
1276 delete $conf->{$opt};
1277 } elsif ($opt eq 'unprivileged') {
1278 die "unable to delete read-only option: '$opt'\n";
1280 die "implement me (delete: $opt)"
1282 write_config
($vmid, $conf) if $running;
1286 # There's no separate swap size to configure, there's memory and "total"
1287 # memory (iow. memory+swap). This means we have to change them together.
1288 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1289 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1290 if (defined($wanted_memory) || defined($wanted_swap)) {
1292 my $old_memory = ($conf->{memory
} || 512);
1293 my $old_swap = ($conf->{swap
} || 0);
1295 $wanted_memory //= $old_memory;
1296 $wanted_swap //= $old_swap;
1298 my $total = $wanted_memory + $wanted_swap;
1300 my $old_total = $old_memory + $old_swap;
1301 if ($total > $old_total) {
1302 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1303 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1305 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1306 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1309 $conf->{memory
} = $wanted_memory;
1310 $conf->{swap
} = $wanted_swap;
1312 write_config
($vmid, $conf) if $running;
1315 foreach my $opt (keys %$param) {
1316 my $value = $param->{$opt};
1317 if ($opt eq 'hostname') {
1318 $conf->{$opt} = $value;
1319 } elsif ($opt eq 'onboot') {
1320 $conf->{$opt} = $value ?
1 : 0;
1321 } elsif ($opt eq 'startup') {
1322 $conf->{$opt} = $value;
1323 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1324 next if $hotplug_error->($opt);
1325 $conf->{$opt} = $value;
1326 } elsif ($opt eq 'nameserver') {
1327 next if $hotplug_error->($opt);
1328 my $list = verify_nameserver_list
($value);
1329 $conf->{$opt} = $list;
1330 } elsif ($opt eq 'searchdomain') {
1331 next if $hotplug_error->($opt);
1332 my $list = verify_searchdomain_list
($value);
1333 $conf->{$opt} = $list;
1334 } elsif ($opt eq 'cpulimit') {
1335 next if $hotplug_error->($opt); # FIXME: hotplug
1336 $conf->{$opt} = $value;
1337 } elsif ($opt eq 'cpuunits') {
1338 $conf->{$opt} = $value;
1339 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1340 } elsif ($opt eq 'description') {
1341 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1342 } elsif ($opt =~ m/^net(\d+)$/) {
1344 my $net = parse_lxc_network
($value);
1346 $conf->{$opt} = print_lxc_network
($net);
1348 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1350 } elsif ($opt eq 'protection') {
1351 $conf->{$opt} = $value ?
1 : 0;
1352 } elsif ($opt =~ m/^mp(\d+)$/) {
1353 next if $hotplug_error->($opt);
1354 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1355 $conf->{$opt} = $value;
1357 } elsif ($opt eq 'rootfs') {
1358 next if $hotplug_error->($opt);
1359 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1360 $conf->{$opt} = $value;
1361 } elsif ($opt eq 'unprivileged') {
1362 die "unable to modify read-only option: '$opt'\n";
1364 die "implement me: $opt";
1366 write_config
($vmid, $conf) if $running;
1369 if (@deleted_volumes) {
1370 my $storage_cfg = PVE
::Storage
::config
();
1371 foreach my $volume (@deleted_volumes) {
1372 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1377 my $storage_cfg = PVE
::Storage
::config
();
1378 create_disks
($storage_cfg, $vmid, $conf, $conf);
1381 # This should be the last thing we do here
1382 if ($running && scalar(@nohotplug)) {
1383 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1387 sub has_dev_console
{
1390 return !(defined($conf->{console
}) && !$conf->{console
});
1396 return $conf->{tty
} // $confdesc->{tty
}->{default};
1402 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1405 sub get_console_command
{
1406 my ($vmid, $conf) = @_;
1408 my $cmode = get_cmode
($conf);
1410 if ($cmode eq 'console') {
1411 return ['lxc-console', '-n', $vmid, '-t', 0];
1412 } elsif ($cmode eq 'tty') {
1413 return ['lxc-console', '-n', $vmid];
1414 } elsif ($cmode eq 'shell') {
1415 return ['lxc-attach', '--clear-env', '-n', $vmid];
1417 die "internal error";
1421 sub get_primary_ips
{
1424 # return data from net0
1426 return undef if !defined($conf->{net0
});
1427 my $net = parse_lxc_network
($conf->{net0
});
1429 my $ipv4 = $net->{ip
};
1431 if ($ipv4 =~ /^(dhcp|manual)$/) {
1437 my $ipv6 = $net->{ip6
};
1439 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1446 return ($ipv4, $ipv6);
1449 sub delete_mountpoint_volume
{
1450 my ($storage_cfg, $vmid, $volume) = @_;
1452 return if classify_mountpoint
($volume) ne 'volume';
1454 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1455 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1458 sub destroy_lxc_container
{
1459 my ($storage_cfg, $vmid, $conf) = @_;
1461 foreach_mountpoint
($conf, sub {
1462 my ($ms, $mountpoint) = @_;
1463 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1466 rmdir "/var/lib/lxc/$vmid/rootfs";
1467 unlink "/var/lib/lxc/$vmid/config";
1468 rmdir "/var/lib/lxc/$vmid";
1469 destroy_config
($vmid);
1471 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1472 #PVE::Tools::run_command($cmd);
1475 sub vm_stop_cleanup
{
1476 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1481 my $vollist = get_vm_volumes
($conf);
1482 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1485 warn $@ if $@; # avoid errors - just warn
1488 my $safe_num_ne = sub {
1491 return 0 if !defined($a) && !defined($b);
1492 return 1 if !defined($a);
1493 return 1 if !defined($b);
1498 my $safe_string_ne = sub {
1501 return 0 if !defined($a) && !defined($b);
1502 return 1 if !defined($a);
1503 return 1 if !defined($b);
1509 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1511 if ($newnet->{type
} ne 'veth') {
1512 # for when there are physical interfaces
1513 die "cannot update interface of type $newnet->{type}";
1516 my $veth = "veth${vmid}i${netid}";
1517 my $eth = $newnet->{name
};
1519 if (my $oldnetcfg = $conf->{$opt}) {
1520 my $oldnet = parse_lxc_network
($oldnetcfg);
1522 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1523 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1525 PVE
::Network
::veth_delete
($veth);
1526 delete $conf->{$opt};
1527 write_config
($vmid, $conf);
1529 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1531 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1532 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1533 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1535 if ($oldnet->{bridge
}) {
1536 PVE
::Network
::tap_unplug
($veth);
1537 foreach (qw(bridge tag firewall)) {
1538 delete $oldnet->{$_};
1540 $conf->{$opt} = print_lxc_network
($oldnet);
1541 write_config
($vmid, $conf);
1544 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1545 foreach (qw(bridge tag firewall)) {
1546 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1548 $conf->{$opt} = print_lxc_network
($oldnet);
1549 write_config
($vmid, $conf);
1552 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1555 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1559 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1561 my $veth = "veth${vmid}i${netid}";
1562 my $vethpeer = $veth . "p";
1563 my $eth = $newnet->{name
};
1565 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1566 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1568 # attach peer in container
1569 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1570 PVE
::Tools
::run_command
($cmd);
1572 # link up peer in container
1573 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1574 PVE
::Tools
::run_command
($cmd);
1576 my $done = { type
=> 'veth' };
1577 foreach (qw(bridge tag firewall hwaddr name)) {
1578 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1580 $conf->{$opt} = print_lxc_network
($done);
1582 write_config
($vmid, $conf);
1585 sub update_ipconfig
{
1586 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1588 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1590 my $optdata = parse_lxc_network
($conf->{$opt});
1594 my $cmdargs = shift;
1595 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1597 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1599 my $change_ip_config = sub {
1600 my ($ipversion) = @_;
1602 my $family_opt = "-$ipversion";
1603 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1604 my $gw= "gw$suffix";
1605 my $ip= "ip$suffix";
1607 my $newip = $newnet->{$ip};
1608 my $newgw = $newnet->{$gw};
1609 my $oldip = $optdata->{$ip};
1611 my $change_ip = &$safe_string_ne($oldip, $newip);
1612 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1614 return if !$change_ip && !$change_gw;
1616 # step 1: add new IP, if this fails we cancel
1617 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1618 if ($change_ip && $is_real_ip) {
1619 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1626 # step 2: replace gateway
1627 # If this fails we delete the added IP and cancel.
1628 # If it succeeds we save the config and delete the old IP, ignoring
1629 # errors. The config is then saved.
1630 # Note: 'ip route replace' can add
1634 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1635 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1637 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1641 # the route was not replaced, the old IP is still available
1642 # rollback (delete new IP) and cancel
1644 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1645 warn $@ if $@; # no need to die here
1650 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1651 # if the route was not deleted, the guest might have deleted it manually
1657 # from this point on we save the configuration
1658 # step 3: delete old IP ignoring errors
1659 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1660 # We need to enable promote_secondaries, otherwise our newly added
1661 # address will be removed along with the old one.
1664 if ($ipversion == 4) {
1665 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1666 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1667 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1669 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1671 warn $@ if $@; # no need to die here
1673 if ($ipversion == 4) {
1674 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1678 foreach my $property ($ip, $gw) {
1679 if ($newnet->{$property}) {
1680 $optdata->{$property} = $newnet->{$property};
1682 delete $optdata->{$property};
1685 $conf->{$opt} = print_lxc_network
($optdata);
1686 write_config
($vmid, $conf);
1687 $lxc_setup->setup_network($conf);
1690 &$change_ip_config(4);
1691 &$change_ip_config(6);
1695 # Internal snapshots
1697 # NOTE: Snapshot create/delete involves several non-atomic
1698 # actions, and can take a long time.
1699 # So we try to avoid locking the file and use the 'lock' variable
1700 # inside the config file instead.
1702 my $snapshot_copy_config = sub {
1703 my ($source, $dest) = @_;
1705 foreach my $k (keys %$source) {
1706 next if $k eq 'snapshots';
1707 next if $k eq 'snapstate';
1708 next if $k eq 'snaptime';
1709 next if $k eq 'vmstate';
1710 next if $k eq 'lock';
1711 next if $k eq 'digest';
1712 next if $k eq 'description';
1714 $dest->{$k} = $source->{$k};
1718 my $snapshot_prepare = sub {
1719 my ($vmid, $snapname, $comment) = @_;
1723 my $updatefn = sub {
1725 my $conf = load_config
($vmid);
1727 die "you can't take a snapshot if it's a template\n"
1728 if is_template
($conf);
1732 $conf->{lock} = 'snapshot';
1734 die "snapshot name '$snapname' already used\n"
1735 if defined($conf->{snapshots
}->{$snapname});
1737 my $storecfg = PVE
::Storage
::config
();
1738 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1739 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1741 $snap = $conf->{snapshots
}->{$snapname} = {};
1743 &$snapshot_copy_config($conf, $snap);
1745 $snap->{'snapstate'} = "prepare";
1746 $snap->{'snaptime'} = time();
1747 $snap->{'description'} = $comment if $comment;
1748 $conf->{snapshots
}->{$snapname} = $snap;
1750 write_config
($vmid, $conf);
1753 lock_config
($vmid, $updatefn);
1758 my $snapshot_commit = sub {
1759 my ($vmid, $snapname) = @_;
1761 my $updatefn = sub {
1763 my $conf = load_config
($vmid);
1765 die "missing snapshot lock\n"
1766 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1768 die "snapshot '$snapname' does not exist\n"
1769 if !defined($conf->{snapshots
}->{$snapname});
1771 die "wrong snapshot state\n"
1772 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1773 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1775 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1776 delete $conf->{lock};
1777 $conf->{parent
} = $snapname;
1779 write_config
($vmid, $conf);
1782 lock_config
($vmid ,$updatefn);
1786 my ($feature, $conf, $storecfg, $snapname) = @_;
1789 my $vzdump = $feature eq 'vzdump';
1790 $feature = 'snapshot' if $vzdump;
1792 foreach_mountpoint
($conf, sub {
1793 my ($ms, $mountpoint) = @_;
1795 return if $err; # skip further test
1796 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1798 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1800 # TODO: implement support for mountpoints
1801 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1805 return $err ?
0 : 1;
1808 my $enter_namespace = sub {
1809 my ($vmid, $pid, $which, $type) = @_;
1810 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1811 or die "failed to open $which namespace of container $vmid: $!\n";
1812 PVE
::Tools
::setns
(fileno($fd), $type)
1813 or die "failed to enter $which namespace of container $vmid: $!\n";
1817 my $do_syncfs = sub {
1818 my ($vmid, $pid, $socket) = @_;
1820 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1822 # Tell the parent process to start reading our /proc/mounts
1823 print {$socket} "go\n";
1826 # Receive /proc/self/mounts
1827 my $mountdata = do { local $/ = undef; <$socket> };
1830 # Now sync all mountpoints...
1831 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1832 foreach my $mp (@$mounts) {
1833 my ($what, $dir, $fs) = @$mp;
1834 next if $fs eq 'fuse.lxcfs';
1835 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1840 sub sync_container_namespace
{
1842 my $pid = find_lxc_pid
($vmid);
1844 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1845 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1846 or die "failed to create socketpair: $!\n";
1849 die "fork failed: $!\n" if !defined($child);
1854 &$do_syncfs($vmid, $pid, $cfd);
1864 die "failed to enter container namespace\n" if $go ne "go\n";
1866 open my $mounts, '<', "/proc/$child/mounts"
1867 or die "failed to open container's /proc/mounts: $!\n";
1868 my $mountdata = do { local $/ = undef; <$mounts> };
1870 print {$pfd} $mountdata;
1873 while (waitpid($child, 0) != $child) {}
1874 die "failed to sync container namespace\n" if $? != 0;
1877 sub snapshot_create
{
1878 my ($vmid, $snapname, $comment) = @_;
1880 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1882 my $conf = load_config
($vmid);
1884 my $running = check_running
($vmid);
1893 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1894 sync_container_namespace
($vmid);
1897 my $storecfg = PVE
::Storage
::config
();
1898 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1899 my $volid = $rootinfo->{volume
};
1901 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1902 $drivehash->{rootfs
} = 1;
1907 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1912 eval { snapshot_delete
($vmid, $snapname, 1, $drivehash); };
1917 &$snapshot_commit($vmid, $snapname);
1920 # Note: $drivehash is only set when called from snapshot_create.
1921 sub snapshot_delete
{
1922 my ($vmid, $snapname, $force, $drivehash) = @_;
1928 my $updatefn = sub {
1930 $conf = load_config
($vmid);
1932 die "you can't delete a snapshot if vm is a template\n"
1933 if is_template
($conf);
1935 $snap = $conf->{snapshots
}->{$snapname};
1941 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1943 $snap->{snapstate
} = 'delete';
1945 write_config
($vmid, $conf);
1948 lock_config
($vmid, $updatefn);
1950 my $storecfg = PVE
::Storage
::config
();
1952 my $unlink_parent = sub {
1954 my ($confref, $new_parent) = @_;
1956 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
1958 $confref->{parent
} = $new_parent;
1960 delete $confref->{parent
};
1965 my $del_snap = sub {
1967 $conf = load_config
($vmid);
1970 delete $conf->{lock};
1975 my $parent = $conf->{snapshots
}->{$snapname}->{parent
};
1976 foreach my $snapkey (keys %{$conf->{snapshots
}}) {
1977 &$unlink_parent($conf->{snapshots
}->{$snapkey}, $parent);
1980 &$unlink_parent($conf, $parent);
1982 delete $conf->{snapshots
}->{$snapname};
1984 write_config
($vmid, $conf);
1987 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1988 my $rootinfo = parse_ct_rootfs
($rootfs);
1989 my $volid = $rootinfo->{volume
};
1992 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1996 if(!$err || ($err && $force)) {
1997 lock_config
($vmid, $del_snap);
1999 die "Can't delete snapshot: $vmid $snapname $err\n";
2004 sub snapshot_rollback
{
2005 my ($vmid, $snapname) = @_;
2007 my $storecfg = PVE
::Storage
::config
();
2009 my $conf = load_config
($vmid);
2011 die "you can't rollback if vm is a template\n" if is_template
($conf);
2013 my $snap = $conf->{snapshots
}->{$snapname};
2015 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2017 my $rootfs = $snap->{rootfs
};
2018 my $rootinfo = parse_ct_rootfs
($rootfs);
2019 my $volid = $rootinfo->{volume
};
2021 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
2023 my $updatefn = sub {
2025 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
2026 if $snap->{snapstate
};
2030 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
2032 die "unable to rollback vm $vmid: vm is running\n"
2033 if check_running
($vmid);
2035 $conf->{lock} = 'rollback';
2039 # copy snapshot config to current config
2041 my $tmp_conf = $conf;
2042 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
2043 $conf->{snapshots
} = $tmp_conf->{snapshots
};
2044 delete $conf->{snaptime
};
2045 delete $conf->{snapname
};
2046 $conf->{parent
} = $snapname;
2048 write_config
($vmid, $conf);
2051 my $unlockfn = sub {
2052 delete $conf->{lock};
2053 write_config
($vmid, $conf);
2056 lock_config
($vmid, $updatefn);
2058 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
2060 lock_config
($vmid, $unlockfn);
2063 sub template_create
{
2064 my ($vmid, $conf) = @_;
2066 my $storecfg = PVE
::Storage
::config
();
2068 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
2069 my $volid = $rootinfo->{volume
};
2071 die "Template feature is not available for '$volid'\n"
2072 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
2074 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2076 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2077 $rootinfo->{volume
} = $template_volid;
2078 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2080 write_config
($vmid, $conf);
2086 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2089 sub mountpoint_names
{
2092 my @names = ('rootfs');
2094 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2095 push @names, "mp$i";
2098 return $reverse ?
reverse @names : @names;
2102 sub foreach_mountpoint_full
{
2103 my ($conf, $reverse, $func) = @_;
2105 foreach my $key (mountpoint_names
($reverse)) {
2106 my $value = $conf->{$key};
2107 next if !defined($value);
2108 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2109 next if !defined($mountpoint);
2111 &$func($key, $mountpoint);
2115 sub foreach_mountpoint
{
2116 my ($conf, $func) = @_;
2118 foreach_mountpoint_full
($conf, 0, $func);
2121 sub foreach_mountpoint_reverse
{
2122 my ($conf, $func) = @_;
2124 foreach_mountpoint_full
($conf, 1, $func);
2127 sub check_ct_modify_config_perm
{
2128 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2130 return 1 if $authuser ne 'root@pam';
2132 foreach my $opt (@$key_list) {
2134 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2135 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2136 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2137 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2138 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2139 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2140 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2141 $opt eq 'searchdomain' || $opt eq 'hostname') {
2142 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2144 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2152 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2154 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2155 my $volid_list = get_vm_volumes
($conf);
2157 foreach_mountpoint_reverse
($conf, sub {
2158 my ($ms, $mountpoint) = @_;
2160 my $volid = $mountpoint->{volume
};
2161 my $mount = $mountpoint->{mp
};
2163 return if !$volid || !$mount;
2165 my $mount_path = "$rootdir/$mount";
2166 $mount_path =~ s!/+!/!g;
2168 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2171 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2184 my ($vmid, $storage_cfg, $conf) = @_;
2186 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2187 File
::Path
::make_path
($rootdir);
2189 my $volid_list = get_vm_volumes
($conf);
2190 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2193 foreach_mountpoint
($conf, sub {
2194 my ($ms, $mountpoint) = @_;
2196 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2200 warn "mounting container failed\n";
2201 umount_all
($vmid, $storage_cfg, $conf, 1);
2209 sub mountpoint_mount_path
{
2210 my ($mountpoint, $storage_cfg, $snapname) = @_;
2212 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2215 my $check_mount_path = sub {
2217 $path = File
::Spec-
>canonpath($path);
2218 my $real = Cwd
::realpath
($path);
2219 if ($real ne $path) {
2220 die "mount path modified by symlink: $path != $real";
2229 if ($line =~ m
@^(/dev/loop\d
+):@) {
2233 my $cmd = ['losetup', '--associated', $path];
2234 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2238 # Run a function with a file attached to a loop device.
2239 # The loop device is always detached afterwards (or set to autoclear).
2240 # Returns the loop device.
2241 sub run_with_loopdev
{
2242 my ($func, $file) = @_;
2246 if ($line =~ m
@^(/dev/loop\d
+)$@) {
2250 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
2251 die "failed to setup loop device for $file\n" if !$device;
2252 eval { &$func($device); };
2254 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
2260 my ($dir, $dest, $ro, @extra_opts) = @_;
2261 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
2263 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
2265 warn "bindmount error\n";
2266 # don't leave writable bind-mounts behind...
2267 PVE
::Tools
::run_command
(['umount', $dest]);
2273 # use $rootdir = undef to just return the corresponding mount path
2274 sub mountpoint_mount
{
2275 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2277 my $volid = $mountpoint->{volume
};
2278 my $mount = $mountpoint->{mp
};
2279 my $type = $mountpoint->{type
};
2280 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
2283 return if !$volid || !$mount;
2287 if (defined($rootdir)) {
2288 $rootdir =~ s!/+$!!;
2289 $mount_path = "$rootdir/$mount";
2290 $mount_path =~ s!/+!/!g;
2291 &$check_mount_path($mount_path);
2292 File
::Path
::mkpath
($mount_path);
2295 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2297 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2300 if (defined($mountpoint->{acl
})) {
2301 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2303 my $readonly = $mountpoint->{ro
};
2305 my @extra_opts = ('-o', $optstring);
2309 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2310 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2312 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2313 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2315 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2317 if ($format eq 'subvol') {
2320 if ($scfg->{type
} eq 'zfspool') {
2321 my $path_arg = $path;
2322 $path_arg =~ s!^/+!!;
2323 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2325 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2328 bindmount
($path, $mount_path, $readonly, @extra_opts);
2329 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
2332 return wantarray ?
($path, 0, $mounted_dev) : $path;
2333 } elsif ($format eq 'raw' || $format eq 'iso') {
2337 if ($format eq 'iso') {
2338 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2339 } elsif ($isBase || defined($snapname)) {
2340 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2343 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
2345 push @extra_opts, '-o', 'ro' if $readonly;
2346 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2350 my $use_loopdev = 0;
2351 if ($scfg->{path
}) {
2352 $mounted_dev = run_with_loopdev
($domount, $path);
2354 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2355 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2356 $mounted_dev = $path;
2359 die "unsupported storage type '$scfg->{type}'\n";
2361 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
2363 die "unsupported image format '$format'\n";
2365 } elsif ($type eq 'device') {
2366 push @extra_opts, '-o', 'ro' if $readonly;
2367 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2368 return wantarray ?
($volid, 0, $volid) : $volid;
2369 } elsif ($type eq 'bind') {
2370 die "directory '$volid' does not exist\n" if ! -d
$volid;
2371 &$check_mount_path($volid);
2372 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
2373 warn "cannot enable quota control for bind mounts\n" if $quota;
2374 return wantarray ?
($volid, 0, undef) : $volid;
2377 die "unsupported storage";
2380 sub get_vm_volumes
{
2381 my ($conf, $excludes) = @_;
2385 foreach_mountpoint
($conf, sub {
2386 my ($ms, $mountpoint) = @_;
2388 return if $excludes && $ms eq $excludes;
2390 my $volid = $mountpoint->{volume
};
2392 return if !$volid || $mountpoint->{type
} ne 'volume';
2394 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2397 push @$vollist, $volid;
2404 my ($dev, $rootuid, $rootgid) = @_;
2406 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2407 '-E', "root_owner=$rootuid:$rootgid",
2412 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2414 if ($volid =~ m!^/dev/.+!) {
2419 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2421 die "cannot format volume '$volid' with no storage\n" if !$storage;
2423 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2425 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2427 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2428 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2430 die "cannot format volume '$volid' (format == $format)\n"
2431 if $format ne 'raw';
2433 mkfs
($path, $rootuid, $rootgid);
2437 my ($storecfg, $vollist) = @_;
2439 foreach my $volid (@$vollist) {
2440 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2446 my ($storecfg, $vmid, $settings, $conf) = @_;
2451 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2452 my $chown_vollist = [];
2454 foreach_mountpoint
($settings, sub {
2455 my ($ms, $mountpoint) = @_;
2457 my $volid = $mountpoint->{volume
};
2458 my $mp = $mountpoint->{mp
};
2460 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2462 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2463 my ($storeid, $size_gb) = ($1, $2);
2465 my $size_kb = int(${size_gb
}*1024) * 1024;
2467 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2468 # fixme: use better naming ct-$vmid-disk-X.raw?
2470 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2472 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2474 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2476 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2478 push @$chown_vollist, $volid;
2480 } elsif ($scfg->{type
} eq 'zfspool') {
2482 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2484 push @$chown_vollist, $volid;
2485 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2487 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2488 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2490 } elsif ($scfg->{type
} eq 'rbd') {
2492 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2493 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2494 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2496 die "unable to create containers on storage type '$scfg->{type}'\n";
2498 push @$vollist, $volid;
2499 $mountpoint->{volume
} = $volid;
2500 $mountpoint->{size
} = $size_kb * 1024;
2501 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2503 # use specified/existing volid/dir/device
2504 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2508 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2509 foreach my $volid (@$chown_vollist) {
2510 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2511 chown($rootuid, $rootgid, $path);
2513 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2515 # free allocated images on error
2517 destroy_disks
($storecfg, $vollist);
2523 # bash completion helper
2525 sub complete_os_templates
{
2526 my ($cmdname, $pname, $cvalue) = @_;
2528 my $cfg = PVE
::Storage
::config
();
2532 if ($cvalue =~ m/^([^:]+):/) {
2536 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2537 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2540 foreach my $id (keys %$data) {
2541 foreach my $item (@{$data->{$id}}) {
2542 push @$res, $item->{volid
} if defined($item->{volid
});
2549 my $complete_ctid_full = sub {
2552 my $idlist = vmstatus
();
2554 my $active_hash = list_active_containers
();
2558 foreach my $id (keys %$idlist) {
2559 my $d = $idlist->{$id};
2560 if (defined($running)) {
2561 next if $d->{template
};
2562 next if $running && !$active_hash->{$id};
2563 next if !$running && $active_hash->{$id};
2572 return &$complete_ctid_full();
2575 sub complete_ctid_stopped
{
2576 return &$complete_ctid_full(0);
2579 sub complete_ctid_running
{
2580 return &$complete_ctid_full(1);
2590 my $lxc = $conf->{lxc
};
2591 foreach my $entry (@$lxc) {
2592 my ($key, $value) = @$entry;
2593 next if $key ne 'lxc.id_map';
2594 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2595 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2596 push @$id_map, [$type, $ct, $host, $length];
2598 $rootuid = $host if $type eq 'u';
2599 $rootgid = $host if $type eq 'g';
2602 die "failed to parse id_map: $value\n";
2606 if (!@$id_map && $conf->{unprivileged
}) {
2607 # Should we read them from /etc/subuid?
2608 $id_map = [ ['u', '0', '100000', '65536'],
2609 ['g', '0', '100000', '65536'] ];
2610 $rootuid = $rootgid = 100000;
2613 return ($id_map, $rootuid, $rootgid);
2616 sub userns_command
{
2619 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];