13 use Fcntl
qw(O_RDONLY);
15 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::Exception
qw(raise_perm_exc);
20 use PVE
::JSONSchema
qw(get_standard_option);
21 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
23 use PVE
::AccessControl
;
26 use Time
::HiRes qw
(gettimeofday
);
30 my $nodename = PVE
::INotify
::nodename
();
32 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
34 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
36 '--xattrs-include=user.*',
37 '--xattrs-include=security.capability',
38 '--warning=no-xattr-write' ];
40 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
46 format
=> 'pve-lxc-mp-string',
47 format_description
=> 'volume',
48 description
=> 'Volume, device or directory to mount into the container.',
52 format_description
=> '[1|0]',
53 description
=> 'Whether to include the mountpoint in backups.',
58 format
=> 'disk-size',
59 format_description
=> 'DiskSize',
60 description
=> 'Volume size (read only value).',
65 format_description
=> 'acl',
66 description
=> 'Explicitly enable or disable ACL support.',
71 format_description
=> 'ro',
72 description
=> 'Read-only mountpoint (not supported with bind mounts)',
77 format_description
=> '[0|1]',
78 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
83 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
84 type
=> 'string', format
=> $rootfs_desc,
85 description
=> "Use volume as container root.",
89 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
90 description
=> "The name of the snapshot.",
91 type
=> 'string', format
=> 'pve-configid',
99 description
=> "Lock/unlock the VM.",
100 enum
=> [qw(migrate backup snapshot rollback)],
105 description
=> "Specifies whether a VM will be started during system bootup.",
108 startup
=> get_standard_option
('pve-startup-order'),
112 description
=> "Enable/disable Template.",
118 enum
=> ['amd64', 'i386'],
119 description
=> "OS architecture type.",
125 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
126 description
=> "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
131 description
=> "Attach a console device (/dev/console) to the container.",
137 description
=> "Specify the number of tty available to the container",
145 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.",
153 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.",
161 description
=> "Amount of RAM for the VM in MB.",
168 description
=> "Amount of SWAP for the VM in MB.",
174 description
=> "Set a host name for the container.",
175 type
=> 'string', format
=> 'dns-name',
181 description
=> "Container description. Only used on the configuration web interface.",
185 type
=> 'string', format
=> 'dns-name-list',
186 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
190 type
=> 'string', format
=> 'address-list',
191 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.",
193 rootfs
=> get_standard_option
('pve-ct-rootfs'),
196 type
=> 'string', format
=> 'pve-configid',
198 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
202 description
=> "Timestamp for snapshots.",
208 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).",
210 enum
=> ['shell', 'console', 'tty'],
216 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
222 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
227 my $valid_lxc_conf_keys = {
231 'lxc.haltsignal' => 1,
232 'lxc.rebootsignal' => 1,
233 'lxc.stopsignal' => 1,
235 'lxc.network.type' => 1,
236 'lxc.network.flags' => 1,
237 'lxc.network.link' => 1,
238 'lxc.network.mtu' => 1,
239 'lxc.network.name' => 1,
240 'lxc.network.hwaddr' => 1,
241 'lxc.network.ipv4' => 1,
242 'lxc.network.ipv4.gateway' => 1,
243 'lxc.network.ipv6' => 1,
244 'lxc.network.ipv6.gateway' => 1,
245 'lxc.network.script.up' => 1,
246 'lxc.network.script.down' => 1,
248 'lxc.console.logfile' => 1,
251 'lxc.devttydir' => 1,
252 'lxc.hook.autodev' => 1,
256 'lxc.mount.entry' => 1,
257 'lxc.mount.auto' => 1,
258 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
259 'lxc.rootfs.mount' => 1,
260 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
261 ', please use mountpoint options in the "rootfs" key',
265 'lxc.aa_profile' => 1,
266 'lxc.aa_allow_incomplete' => 1,
267 'lxc.se_context' => 1,
270 'lxc.hook.pre-start' => 1,
271 'lxc.hook.pre-mount' => 1,
272 'lxc.hook.mount' => 1,
273 'lxc.hook.start' => 1,
274 'lxc.hook.stop' => 1,
275 'lxc.hook.post-stop' => 1,
276 'lxc.hook.clone' => 1,
277 'lxc.hook.destroy' => 1,
280 'lxc.start.auto' => 1,
281 'lxc.start.delay' => 1,
282 'lxc.start.order' => 1,
284 'lxc.environment' => 1,
291 description
=> "Network interface type.",
296 format_description
=> 'String',
297 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
298 pattern
=> '[-_.\w\d]+',
302 format_description
=> 'vmbr<Number>',
303 description
=> 'Bridge to attach the network device to.',
304 pattern
=> '[-_.\w\d]+',
309 format_description
=> 'MAC',
310 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
311 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
316 format_description
=> 'Number',
317 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
318 minimum
=> 64, # minimum ethernet frame is 64 bytes
323 format
=> 'pve-ipv4-config',
324 format_description
=> 'IPv4Format/CIDR',
325 description
=> 'IPv4 address in CIDR format.',
331 format_description
=> 'GatewayIPv4',
332 description
=> 'Default gateway for IPv4 traffic.',
337 format
=> 'pve-ipv6-config',
338 format_description
=> 'IPv6Format/CIDR',
339 description
=> 'IPv6 address in CIDR format.',
345 format_description
=> 'GatewayIPv6',
346 description
=> 'Default gateway for IPv6 traffic.',
351 format_description
=> '[1|0]',
352 description
=> "Controls whether this interface's firewall rules should be used.",
357 format_description
=> 'VlanNo',
360 description
=> "VLAN tag for this interface.",
365 pattern
=> qr/\d+(?:;\d+)*/,
366 format_description
=> 'vlanid[;vlanid...]',
367 description
=> "VLAN ids to pass through the interface",
371 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
373 my $MAX_LXC_NETWORKS = 10;
374 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
375 $confdesc->{"net$i"} = {
377 type
=> 'string', format
=> $netconf_desc,
378 description
=> "Specifies network interfaces for the container.",
382 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
383 sub verify_lxc_mp_string
{
384 my ($mp, $noerr) = @_;
388 # /. or /.. at the end
389 # ../ at the beginning
391 if($mp =~ m
@/\.\
.?
/@ ||
394 return undef if $noerr;
395 die "$mp contains illegal character sequences\n";
404 format
=> 'pve-lxc-mp-string',
405 format_description
=> 'Path',
406 description
=> 'Path to the mountpoint as seen from inside the container.',
409 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
413 type
=> 'string', format
=> 'pve-volume-id',
414 description
=> "Reference to unused volumes.",
417 my $MAX_MOUNT_POINTS = 10;
418 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
419 $confdesc->{"mp$i"} = {
421 type
=> 'string', format
=> $mp_desc,
422 description
=> "Use volume as container mount point (experimental feature).",
427 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
428 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
429 $confdesc->{"unused$i"} = $unuseddesc;
432 sub write_pct_config
{
433 my ($filename, $conf) = @_;
435 delete $conf->{snapstate
}; # just to be sure
436 my $volidlist = get_vm_volumes
($conf);
437 my $used_volids = {};
438 foreach my $vid (@$volidlist) {
439 $used_volids->{$vid} = 1;
442 # remove 'unusedX' settings if the volume is still used
443 foreach my $key (keys %$conf) {
444 my $value = $conf->{$key};
445 if ($key =~ m/^unused/ && $used_volids->{$value}) {
446 delete $conf->{$key};
450 my $generate_raw_config = sub {
455 # add description as comment to top of file
456 my $descr = $conf->{description
} || '';
457 foreach my $cl (split(/\n/, $descr)) {
458 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
461 foreach my $key (sort keys %$conf) {
462 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
463 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
464 my $value = $conf->{$key};
465 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
466 $raw .= "$key: $value\n";
469 if (my $lxcconf = $conf->{lxc
}) {
470 foreach my $entry (@$lxcconf) {
471 my ($k, $v) = @$entry;
479 my $raw = &$generate_raw_config($conf);
481 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
482 $raw .= "\n[$snapname]\n";
483 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
490 my ($key, $value) = @_;
492 die "unknown setting '$key'\n" if !$confdesc->{$key};
494 my $type = $confdesc->{$key}->{type
};
496 if (!defined($value)) {
497 die "got undefined value\n";
500 if ($value =~ m/[\n\r]/) {
501 die "property contains a line feed\n";
504 if ($type eq 'boolean') {
505 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
506 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
507 die "type check ('boolean') failed - got '$value'\n";
508 } elsif ($type eq 'integer') {
509 return int($1) if $value =~ m/^(\d+)$/;
510 die "type check ('integer') failed - got '$value'\n";
511 } elsif ($type eq 'number') {
512 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
513 die "type check ('number') failed - got '$value'\n";
514 } elsif ($type eq 'string') {
515 if (my $fmt = $confdesc->{$key}->{format
}) {
516 PVE
::JSONSchema
::check_format
($fmt, $value);
525 sub parse_pct_config
{
526 my ($filename, $raw) = @_;
528 return undef if !defined($raw);
531 digest
=> Digest
::SHA
::sha1_hex
($raw),
535 $filename =~ m
|/lxc/(\d
+).conf
$|
536 || die "got strange filename '$filename'";
544 my @lines = split(/\n/, $raw);
545 foreach my $line (@lines) {
546 next if $line =~ m/^\s*$/;
548 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
550 $conf->{description
} = $descr if $descr;
552 $conf = $res->{snapshots
}->{$section} = {};
556 if ($line =~ m/^\#(.*)\s*$/) {
557 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
561 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
564 my $validity = $valid_lxc_conf_keys->{$key} || 0;
565 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
566 push @{$conf->{lxc
}}, [$key, $value];
567 } elsif (my $errmsg = $validity) {
568 warn "vm $vmid - $key: $errmsg\n";
570 warn "vm $vmid - unable to parse config: $line\n";
572 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
573 $descr .= PVE
::Tools
::decode_text
($2);
574 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
575 $conf->{snapstate
} = $1;
576 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
579 eval { $value = check_type
($key, $value); };
580 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
581 $conf->{$key} = $value;
583 warn "vm $vmid - unable to parse config: $line\n";
587 $conf->{description
} = $descr if $descr;
589 delete $res->{snapstate
}; # just to be sure
595 my $vmlist = PVE
::Cluster
::get_vmlist
();
597 return $res if !$vmlist || !$vmlist->{ids
};
598 my $ids = $vmlist->{ids
};
600 foreach my $vmid (keys %$ids) {
601 next if !$vmid; # skip CT0
602 my $d = $ids->{$vmid};
603 next if !$d->{node
} || $d->{node
} ne $nodename;
604 next if !$d->{type
} || $d->{type
} ne 'lxc';
605 $res->{$vmid}->{type
} = 'lxc';
613 unlink PVE
::LXC
::Config-
>config_file($vmid, $nodename);
619 return defined($confdesc->{$name});
622 # add JSON properties for create and set function
623 sub json_config_properties
{
626 foreach my $opt (keys %$confdesc) {
627 next if $opt eq 'parent' || $opt eq 'snaptime';
628 next if $prop->{$opt};
629 $prop->{$opt} = $confdesc->{$opt};
635 # container status helpers
637 sub list_active_containers
{
639 my $filename = "/proc/net/unix";
641 # similar test is used by lcxcontainers.c: list_active_containers
644 my $fh = IO
::File-
>new ($filename, "r");
647 while (defined(my $line = <$fh>)) {
648 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
650 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
661 # warning: this is slow
665 my $active_hash = list_active_containers
();
667 return 1 if defined($active_hash->{$vmid});
672 sub get_container_disk_usage
{
673 my ($vmid, $pid) = @_;
675 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
678 my $last_proc_vmid_stat;
680 my $parse_cpuacct_stat = sub {
683 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
687 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
700 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
702 my $active_hash = list_active_containers
();
704 my $cpucount = $cpuinfo->{cpus
} || 1;
706 my $cdtime = gettimeofday
;
708 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
710 foreach my $vmid (keys %$list) {
711 my $d = $list->{$vmid};
713 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
714 warn $@ if $@; # ignore errors (consider them stopped)
716 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
718 my $cfspath = PVE
::LXC
::Config-
>cfs_config_path($vmid);
719 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
721 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
722 $d->{name
} =~ s/[\s]//g;
724 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
726 $d->{lock} = $conf->{lock} || '';
729 my $res = get_container_disk_usage
($vmid, $d->{pid
});
730 $d->{disk
} = $res->{used
};
731 $d->{maxdisk
} = $res->{total
};
734 # use 4GB by default ??
735 if (my $rootfs = $conf->{rootfs
}) {
736 my $rootinfo = parse_ct_rootfs
($rootfs);
737 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
739 $d->{maxdisk
} = 4*1024*1024*1024;
745 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
746 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
757 $d->{template
} = PVE
::LXC
::Config-
>is_template($conf);
760 foreach my $vmid (keys %$list) {
761 my $d = $list->{$vmid};
764 next if !$pid; # skip stopped CTs
766 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
767 $d->{uptime
} = time - $ctime; # the method lxcfs uses
769 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
770 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
772 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
773 my @bytes = split(/\n/, $blkio_bytes);
774 foreach my $byte (@bytes) {
775 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
776 $d->{diskread
} = $2 if $key eq 'Read';
777 $d->{diskwrite
} = $2 if $key eq 'Write';
781 my $pstat = &$parse_cpuacct_stat($vmid);
783 my $used = $pstat->{utime} + $pstat->{stime
};
785 my $old = $last_proc_vmid_stat->{$vmid};
787 $last_proc_vmid_stat->{$vmid} = {
795 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
798 my $dutime = $used - $old->{used
};
800 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
801 $last_proc_vmid_stat->{$vmid} = {
807 $d->{cpu
} = $old->{cpu
};
811 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
813 foreach my $dev (keys %$netdev) {
814 next if $dev !~ m/^veth([1-9]\d*)i/;
816 my $d = $list->{$vmid};
820 $d->{netout
} += $netdev->{$dev}->{receive
};
821 $d->{netin
} += $netdev->{$dev}->{transmit
};
828 sub classify_mountpoint
{
831 return 'device' if $vol =~ m!^/dev/!;
837 my $parse_ct_mountpoint_full = sub {
838 my ($desc, $data, $noerr) = @_;
843 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
845 return undef if $noerr;
849 if (defined(my $size = $res->{size
})) {
850 $size = PVE
::JSONSchema
::parse_size
($size);
851 if (!defined($size)) {
852 return undef if $noerr;
853 die "invalid size: $size\n";
855 $res->{size
} = $size;
858 $res->{type
} = classify_mountpoint
($res->{volume
});
863 sub parse_ct_rootfs
{
864 my ($data, $noerr) = @_;
866 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
868 $res->{mp
} = '/' if defined($res);
873 sub parse_ct_mountpoint
{
874 my ($data, $noerr) = @_;
876 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
879 sub print_ct_mountpoint
{
880 my ($info, $nomp) = @_;
881 my $skip = [ 'type' ];
882 push @$skip, 'mp' if $nomp;
883 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
886 sub print_lxc_network
{
888 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
891 sub parse_lxc_network
{
896 return $res if !$data;
898 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
900 $res->{type
} = 'veth';
901 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
906 sub read_cgroup_value
{
907 my ($group, $vmid, $name, $full) = @_;
909 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
911 return PVE
::Tools
::file_get_contents
($path) if $full;
913 return PVE
::Tools
::file_read_firstline
($path);
916 sub write_cgroup_value
{
917 my ($group, $vmid, $name, $value) = @_;
919 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
920 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
924 sub find_lxc_console_pids
{
928 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
931 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
934 my @args = split(/\0/, $cmdline);
936 # search for lxc-console -n <vmid>
937 return if scalar(@args) != 3;
938 return if $args[1] ne '-n';
939 return if $args[2] !~ m/^\d+$/;
940 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
944 push @{$res->{$vmid}}, $pid;
956 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
958 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
960 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
965 # Note: we cannot use Net:IP, because that only allows strict
967 sub parse_ipv4_cidr
{
968 my ($cidr, $noerr) = @_;
970 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
971 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
974 return undef if $noerr;
976 die "unable to parse ipv4 address/mask\n";
980 sub update_lxc_config
{
981 my ($storage_cfg, $vmid, $conf) = @_;
983 my $dir = "/var/lib/lxc/$vmid";
985 if ($conf->{template
}) {
987 unlink "$dir/config";
994 die "missing 'arch' - internal error" if !$conf->{arch
};
995 $raw .= "lxc.arch = $conf->{arch}\n";
997 my $unprivileged = $conf->{unprivileged
};
998 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1000 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1001 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) {
1002 my $inc ="/usr/share/lxc/config/$ostype.common.conf";
1003 $inc ="/usr/share/lxc/config/common.conf" if !-f
$inc;
1004 $raw .= "lxc.include = $inc\n";
1005 if ($unprivileged || $custom_idmap) {
1006 $inc = "/usr/share/lxc/config/$ostype.userns.conf";
1007 $inc = "/usr/share/lxc/config/userns.conf" if !-f
$inc;
1008 $raw .= "lxc.include = $inc\n"
1011 die "implement me (ostype $ostype)";
1014 # WARNING: DO NOT REMOVE this without making sure that loop device nodes
1015 # cannot be exposed to the container with r/w access (cgroup perms).
1016 # When this is enabled mounts will still remain in the monitor's namespace
1017 # after the container unmounted them and thus will not detach from their
1018 # files while the container is running!
1019 $raw .= "lxc.monitor.unshare = 1\n";
1021 # Should we read them from /etc/subuid?
1022 if ($unprivileged && !$custom_idmap) {
1023 $raw .= "lxc.id_map = u 0 100000 65536\n";
1024 $raw .= "lxc.id_map = g 0 100000 65536\n";
1027 if (!has_dev_console
($conf)) {
1028 $raw .= "lxc.console = none\n";
1029 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1032 my $ttycount = get_tty_count
($conf);
1033 $raw .= "lxc.tty = $ttycount\n";
1035 # some init scripts expect a linux terminal (turnkey).
1036 $raw .= "lxc.environment = TERM=linux\n";
1038 my $utsname = $conf->{hostname
} || "CT$vmid";
1039 $raw .= "lxc.utsname = $utsname\n";
1041 my $memory = $conf->{memory
} || 512;
1042 my $swap = $conf->{swap
} // 0;
1044 my $lxcmem = int($memory*1024*1024);
1045 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1047 my $lxcswap = int(($memory + $swap)*1024*1024);
1048 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1050 if (my $cpulimit = $conf->{cpulimit
}) {
1051 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1052 my $value = int(100000*$cpulimit);
1053 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1056 my $shares = $conf->{cpuunits
} || 1024;
1057 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1059 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1061 $raw .= "lxc.rootfs = $dir/rootfs\n";
1064 foreach my $k (keys %$conf) {
1065 next if $k !~ m/^net(\d+)$/;
1067 my $d = parse_lxc_network
($conf->{$k});
1069 $raw .= "lxc.network.type = veth\n";
1070 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1071 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1072 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1073 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1076 if (my $lxcconf = $conf->{lxc
}) {
1077 foreach my $entry (@$lxcconf) {
1078 my ($k, $v) = @$entry;
1079 $netcount++ if $k eq 'lxc.network.type';
1080 $raw .= "$k = $v\n";
1084 $raw .= "lxc.network.type = empty\n" if !$netcount;
1086 File
::Path
::mkpath
("$dir/rootfs");
1088 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1091 # verify and cleanup nameserver list (replace \0 with ' ')
1092 sub verify_nameserver_list
{
1093 my ($nameserver_list) = @_;
1096 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1097 PVE
::JSONSchema
::pve_verify_ip
($server);
1098 push @list, $server;
1101 return join(' ', @list);
1104 sub verify_searchdomain_list
{
1105 my ($searchdomain_list) = @_;
1108 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1109 # todo: should we add checks for valid dns domains?
1110 push @list, $server;
1113 return join(' ', @list);
1116 sub is_volume_in_use
{
1117 my ($config, $volid, $include_snapshots) = @_;
1120 foreach_mountpoint
($config, sub {
1121 my ($ms, $mountpoint) = @_;
1123 if ($mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid) {
1128 my $snapshots = $config->{snapshots
};
1129 if ($include_snapshots && $snapshots) {
1130 foreach my $snap (keys %$snapshots) {
1131 $used ||= is_volume_in_use
($snapshots->{$snap}, $volid);
1138 sub add_unused_volume
{
1139 my ($config, $volid) = @_;
1142 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1143 my $test = "unused$ind";
1144 if (my $vid = $config->{$test}) {
1145 return if $vid eq $volid; # do not add duplicates
1151 die "Too many unused volumes - please delete them first.\n" if !$key;
1153 $config->{$key} = $volid;
1158 sub update_pct_config
{
1159 my ($vmid, $conf, $running, $param, $delete) = @_;
1164 my @deleted_volumes;
1168 my $pid = find_lxc_pid
($vmid);
1169 $rootdir = "/proc/$pid/root";
1172 my $hotplug_error = sub {
1174 push @nohotplug, @_;
1181 if (defined($delete)) {
1182 foreach my $opt (@$delete) {
1183 if (!exists($conf->{$opt})) {
1184 warn "no such option: $opt\n";
1188 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1189 die "unable to delete required option '$opt'\n";
1190 } elsif ($opt eq 'swap') {
1191 delete $conf->{$opt};
1192 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1193 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1194 delete $conf->{$opt};
1195 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1196 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1197 next if $hotplug_error->($opt);
1198 delete $conf->{$opt};
1199 } elsif ($opt =~ m/^net(\d)$/) {
1200 delete $conf->{$opt};
1203 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1204 } elsif ($opt eq 'protection') {
1205 delete $conf->{$opt};
1206 } elsif ($opt =~ m/^unused(\d+)$/) {
1207 next if $hotplug_error->($opt);
1208 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1209 push @deleted_volumes, $conf->{$opt};
1210 delete $conf->{$opt};
1211 } elsif ($opt =~ m/^mp(\d+)$/) {
1212 next if $hotplug_error->($opt);
1213 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1214 my $mp = parse_ct_mountpoint
($conf->{$opt});
1215 delete $conf->{$opt};
1216 if ($mp->{type
} eq 'volume') {
1217 add_unused_volume
($conf, $mp->{volume
});
1219 } elsif ($opt eq 'unprivileged') {
1220 die "unable to delete read-only option: '$opt'\n";
1222 die "implement me (delete: $opt)"
1224 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1228 # There's no separate swap size to configure, there's memory and "total"
1229 # memory (iow. memory+swap). This means we have to change them together.
1230 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1231 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1232 if (defined($wanted_memory) || defined($wanted_swap)) {
1234 my $old_memory = ($conf->{memory
} || 512);
1235 my $old_swap = ($conf->{swap
} || 0);
1237 $wanted_memory //= $old_memory;
1238 $wanted_swap //= $old_swap;
1240 my $total = $wanted_memory + $wanted_swap;
1242 my $old_total = $old_memory + $old_swap;
1243 if ($total > $old_total) {
1244 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1245 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1247 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1248 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1251 $conf->{memory
} = $wanted_memory;
1252 $conf->{swap
} = $wanted_swap;
1254 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1257 my $used_volids = {};
1259 foreach my $opt (keys %$param) {
1260 my $value = $param->{$opt};
1261 if ($opt eq 'hostname') {
1262 $conf->{$opt} = $value;
1263 } elsif ($opt eq 'onboot') {
1264 $conf->{$opt} = $value ?
1 : 0;
1265 } elsif ($opt eq 'startup') {
1266 $conf->{$opt} = $value;
1267 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1268 next if $hotplug_error->($opt);
1269 $conf->{$opt} = $value;
1270 } elsif ($opt eq 'nameserver') {
1271 next if $hotplug_error->($opt);
1272 my $list = verify_nameserver_list
($value);
1273 $conf->{$opt} = $list;
1274 } elsif ($opt eq 'searchdomain') {
1275 next if $hotplug_error->($opt);
1276 my $list = verify_searchdomain_list
($value);
1277 $conf->{$opt} = $list;
1278 } elsif ($opt eq 'cpulimit') {
1279 next if $hotplug_error->($opt); # FIXME: hotplug
1280 $conf->{$opt} = $value;
1281 } elsif ($opt eq 'cpuunits') {
1282 $conf->{$opt} = $value;
1283 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1284 } elsif ($opt eq 'description') {
1285 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1286 } elsif ($opt =~ m/^net(\d+)$/) {
1288 my $net = parse_lxc_network
($value);
1290 $conf->{$opt} = print_lxc_network
($net);
1292 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1294 } elsif ($opt eq 'protection') {
1295 $conf->{$opt} = $value ?
1 : 0;
1296 } elsif ($opt =~ m/^mp(\d+)$/) {
1297 next if $hotplug_error->($opt);
1298 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1299 my $old = $conf->{$opt};
1300 $conf->{$opt} = $value;
1301 if (defined($old)) {
1302 my $mp = parse_ct_mountpoint
($old);
1303 if ($mp->{type
} eq 'volume') {
1304 add_unused_volume
($conf, $mp->{volume
});
1308 my $mp = parse_ct_mountpoint
($value);
1309 $used_volids->{$mp->{volume
}} = 1;
1310 } elsif ($opt eq 'rootfs') {
1311 next if $hotplug_error->($opt);
1312 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1313 my $old = $conf->{$opt};
1314 $conf->{$opt} = $value;
1315 if (defined($old)) {
1316 my $mp = parse_ct_rootfs
($old);
1317 if ($mp->{type
} eq 'volume') {
1318 add_unused_volume
($conf, $mp->{volume
});
1321 my $mp = parse_ct_rootfs
($value);
1322 $used_volids->{$mp->{volume
}} = 1;
1323 } elsif ($opt eq 'unprivileged') {
1324 die "unable to modify read-only option: '$opt'\n";
1325 } elsif ($opt eq 'ostype') {
1326 next if $hotplug_error->($opt);
1327 $conf->{$opt} = $value;
1329 die "implement me: $opt";
1331 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1334 # Apply deletions and creations of new volumes
1335 if (@deleted_volumes) {
1336 my $storage_cfg = PVE
::Storage
::config
();
1337 foreach my $volume (@deleted_volumes) {
1338 next if $used_volids->{$volume}; # could have been re-added, too
1339 # also check for references in snapshots
1340 next if is_volume_in_use
($conf, $volume, 1);
1341 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1346 my $storage_cfg = PVE
::Storage
::config
();
1347 create_disks
($storage_cfg, $vmid, $conf, $conf);
1350 # This should be the last thing we do here
1351 if ($running && scalar(@nohotplug)) {
1352 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1356 sub has_dev_console
{
1359 return !(defined($conf->{console
}) && !$conf->{console
});
1365 return $conf->{tty
} // $confdesc->{tty
}->{default};
1371 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1374 sub get_console_command
{
1375 my ($vmid, $conf) = @_;
1377 my $cmode = get_cmode
($conf);
1379 if ($cmode eq 'console') {
1380 return ['lxc-console', '-n', $vmid, '-t', 0];
1381 } elsif ($cmode eq 'tty') {
1382 return ['lxc-console', '-n', $vmid];
1383 } elsif ($cmode eq 'shell') {
1384 return ['lxc-attach', '--clear-env', '-n', $vmid];
1386 die "internal error";
1390 sub get_primary_ips
{
1393 # return data from net0
1395 return undef if !defined($conf->{net0
});
1396 my $net = parse_lxc_network
($conf->{net0
});
1398 my $ipv4 = $net->{ip
};
1400 if ($ipv4 =~ /^(dhcp|manual)$/) {
1406 my $ipv6 = $net->{ip6
};
1408 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1415 return ($ipv4, $ipv6);
1418 sub delete_mountpoint_volume
{
1419 my ($storage_cfg, $vmid, $volume) = @_;
1421 return if classify_mountpoint
($volume) ne 'volume';
1423 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1424 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1427 sub destroy_lxc_container
{
1428 my ($storage_cfg, $vmid, $conf) = @_;
1430 foreach_mountpoint
($conf, sub {
1431 my ($ms, $mountpoint) = @_;
1432 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1435 rmdir "/var/lib/lxc/$vmid/rootfs";
1436 unlink "/var/lib/lxc/$vmid/config";
1437 rmdir "/var/lib/lxc/$vmid";
1438 destroy_config
($vmid);
1440 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1441 #PVE::Tools::run_command($cmd);
1444 sub vm_stop_cleanup
{
1445 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1450 my $vollist = get_vm_volumes
($conf);
1451 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1454 warn $@ if $@; # avoid errors - just warn
1457 my $safe_num_ne = sub {
1460 return 0 if !defined($a) && !defined($b);
1461 return 1 if !defined($a);
1462 return 1 if !defined($b);
1467 my $safe_string_ne = sub {
1470 return 0 if !defined($a) && !defined($b);
1471 return 1 if !defined($a);
1472 return 1 if !defined($b);
1478 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1480 if ($newnet->{type
} ne 'veth') {
1481 # for when there are physical interfaces
1482 die "cannot update interface of type $newnet->{type}";
1485 my $veth = "veth${vmid}i${netid}";
1486 my $eth = $newnet->{name
};
1488 if (my $oldnetcfg = $conf->{$opt}) {
1489 my $oldnet = parse_lxc_network
($oldnetcfg);
1491 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1492 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1494 PVE
::Network
::veth_delete
($veth);
1495 delete $conf->{$opt};
1496 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1498 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1500 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1501 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1502 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1504 if ($oldnet->{bridge
}) {
1505 PVE
::Network
::tap_unplug
($veth);
1506 foreach (qw(bridge tag firewall)) {
1507 delete $oldnet->{$_};
1509 $conf->{$opt} = print_lxc_network
($oldnet);
1510 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1513 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1514 foreach (qw(bridge tag firewall)) {
1515 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1517 $conf->{$opt} = print_lxc_network
($oldnet);
1518 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1521 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1524 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1528 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1530 my $veth = "veth${vmid}i${netid}";
1531 my $vethpeer = $veth . "p";
1532 my $eth = $newnet->{name
};
1534 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1535 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1537 # attach peer in container
1538 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1539 PVE
::Tools
::run_command
($cmd);
1541 # link up peer in container
1542 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1543 PVE
::Tools
::run_command
($cmd);
1545 my $done = { type
=> 'veth' };
1546 foreach (qw(bridge tag firewall hwaddr name)) {
1547 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1549 $conf->{$opt} = print_lxc_network
($done);
1551 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1554 sub update_ipconfig
{
1555 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1557 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1559 my $optdata = parse_lxc_network
($conf->{$opt});
1563 my $cmdargs = shift;
1564 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1566 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1568 my $change_ip_config = sub {
1569 my ($ipversion) = @_;
1571 my $family_opt = "-$ipversion";
1572 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1573 my $gw= "gw$suffix";
1574 my $ip= "ip$suffix";
1576 my $newip = $newnet->{$ip};
1577 my $newgw = $newnet->{$gw};
1578 my $oldip = $optdata->{$ip};
1580 my $change_ip = &$safe_string_ne($oldip, $newip);
1581 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1583 return if !$change_ip && !$change_gw;
1585 # step 1: add new IP, if this fails we cancel
1586 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1587 if ($change_ip && $is_real_ip) {
1588 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1595 # step 2: replace gateway
1596 # If this fails we delete the added IP and cancel.
1597 # If it succeeds we save the config and delete the old IP, ignoring
1598 # errors. The config is then saved.
1599 # Note: 'ip route replace' can add
1603 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1604 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1606 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1610 # the route was not replaced, the old IP is still available
1611 # rollback (delete new IP) and cancel
1613 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1614 warn $@ if $@; # no need to die here
1619 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1620 # if the route was not deleted, the guest might have deleted it manually
1626 # from this point on we save the configuration
1627 # step 3: delete old IP ignoring errors
1628 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1629 # We need to enable promote_secondaries, otherwise our newly added
1630 # address will be removed along with the old one.
1633 if ($ipversion == 4) {
1634 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1635 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1636 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1638 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1640 warn $@ if $@; # no need to die here
1642 if ($ipversion == 4) {
1643 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1647 foreach my $property ($ip, $gw) {
1648 if ($newnet->{$property}) {
1649 $optdata->{$property} = $newnet->{$property};
1651 delete $optdata->{$property};
1654 $conf->{$opt} = print_lxc_network
($optdata);
1655 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1656 $lxc_setup->setup_network($conf);
1659 &$change_ip_config(4);
1660 &$change_ip_config(6);
1664 my $enter_namespace = sub {
1665 my ($vmid, $pid, $which, $type) = @_;
1666 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1667 or die "failed to open $which namespace of container $vmid: $!\n";
1668 PVE
::Tools
::setns
(fileno($fd), $type)
1669 or die "failed to enter $which namespace of container $vmid: $!\n";
1673 my $do_syncfs = sub {
1674 my ($vmid, $pid, $socket) = @_;
1676 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1678 # Tell the parent process to start reading our /proc/mounts
1679 print {$socket} "go\n";
1682 # Receive /proc/self/mounts
1683 my $mountdata = do { local $/ = undef; <$socket> };
1686 # Now sync all mountpoints...
1687 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1688 foreach my $mp (@$mounts) {
1689 my ($what, $dir, $fs) = @$mp;
1690 next if $fs eq 'fuse.lxcfs';
1691 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1696 sub sync_container_namespace
{
1698 my $pid = find_lxc_pid
($vmid);
1700 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1701 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1702 or die "failed to create socketpair: $!\n";
1705 die "fork failed: $!\n" if !defined($child);
1710 &$do_syncfs($vmid, $pid, $cfd);
1720 die "failed to enter container namespace\n" if $go ne "go\n";
1722 open my $mounts, '<', "/proc/$child/mounts"
1723 or die "failed to open container's /proc/mounts: $!\n";
1724 my $mountdata = do { local $/ = undef; <$mounts> };
1726 print {$pfd} $mountdata;
1729 while (waitpid($child, 0) != $child) {}
1730 die "failed to sync container namespace\n" if $? != 0;
1733 sub template_create
{
1734 my ($vmid, $conf) = @_;
1736 my $storecfg = PVE
::Storage
::config
();
1738 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1739 my $volid = $rootinfo->{volume
};
1741 die "Template feature is not available for '$volid'\n"
1742 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1744 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1746 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1747 $rootinfo->{volume
} = $template_volid;
1748 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1750 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1753 sub mountpoint_names
{
1756 my @names = ('rootfs');
1758 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1759 push @names, "mp$i";
1762 return $reverse ?
reverse @names : @names;
1766 sub foreach_mountpoint_full
{
1767 my ($conf, $reverse, $func) = @_;
1769 foreach my $key (mountpoint_names
($reverse)) {
1770 my $value = $conf->{$key};
1771 next if !defined($value);
1772 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
1773 next if !defined($mountpoint);
1775 &$func($key, $mountpoint);
1779 sub foreach_mountpoint
{
1780 my ($conf, $func) = @_;
1782 foreach_mountpoint_full
($conf, 0, $func);
1785 sub foreach_mountpoint_reverse
{
1786 my ($conf, $func) = @_;
1788 foreach_mountpoint_full
($conf, 1, $func);
1791 sub check_ct_modify_config_perm
{
1792 my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
1794 return 1 if $authuser eq 'root@pam';
1797 my ($opt, $delete) = @_;
1798 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1799 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1800 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1801 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1803 my $data = $opt eq 'rootfs' ? parse_ct_rootfs
($newconf->{$opt})
1804 : parse_ct_mountpoint
($newconf->{$opt});
1805 raise_perm_exc
("mountpoint type $data->{type}") if $data->{type
} ne 'volume';
1806 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1807 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1808 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1809 $opt eq 'searchdomain' || $opt eq 'hostname') {
1810 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1812 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1816 foreach my $opt (keys %$newconf) {
1819 foreach my $opt (@$delete) {
1827 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1829 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1830 my $volid_list = get_vm_volumes
($conf);
1832 foreach_mountpoint_reverse
($conf, sub {
1833 my ($ms, $mountpoint) = @_;
1835 my $volid = $mountpoint->{volume
};
1836 my $mount = $mountpoint->{mp
};
1838 return if !$volid || !$mount;
1840 my $mount_path = "$rootdir/$mount";
1841 $mount_path =~ s!/+!/!g;
1843 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1846 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1859 my ($vmid, $storage_cfg, $conf) = @_;
1861 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1862 File
::Path
::make_path
($rootdir);
1864 my $volid_list = get_vm_volumes
($conf);
1865 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1868 foreach_mountpoint
($conf, sub {
1869 my ($ms, $mountpoint) = @_;
1871 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
1875 warn "mounting container failed\n";
1876 umount_all
($vmid, $storage_cfg, $conf, 1);
1884 sub mountpoint_mount_path
{
1885 my ($mountpoint, $storage_cfg, $snapname) = @_;
1887 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
1890 my $check_mount_path = sub {
1892 $path = File
::Spec-
>canonpath($path);
1893 my $real = Cwd
::realpath
($path);
1894 if ($real ne $path) {
1895 die "mount path modified by symlink: $path != $real";
1904 if ($line =~ m
@^(/dev/loop\d
+):@) {
1908 my $cmd = ['losetup', '--associated', $path];
1909 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
1913 # Run a function with a file attached to a loop device.
1914 # The loop device is always detached afterwards (or set to autoclear).
1915 # Returns the loop device.
1916 sub run_with_loopdev
{
1917 my ($func, $file) = @_;
1918 my $device = query_loopdev
($file);
1919 # Try to reuse an existing device
1921 # We assume that whoever setup the loop device is responsible for
1929 if ($line =~ m
@^(/dev/loop\d
+)$@) {
1933 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
1934 die "failed to setup loop device for $file\n" if !$device;
1935 eval { &$func($device); };
1937 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
1943 my ($dir, $dest, $ro, @extra_opts) = @_;
1944 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
1946 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
1948 warn "bindmount error\n";
1949 # don't leave writable bind-mounts behind...
1950 PVE
::Tools
::run_command
(['umount', $dest]);
1956 # use $rootdir = undef to just return the corresponding mount path
1957 sub mountpoint_mount
{
1958 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1960 my $volid = $mountpoint->{volume
};
1961 my $mount = $mountpoint->{mp
};
1962 my $type = $mountpoint->{type
};
1963 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
1966 return if !$volid || !$mount;
1970 if (defined($rootdir)) {
1971 $rootdir =~ s!/+$!!;
1972 $mount_path = "$rootdir/$mount";
1973 $mount_path =~ s!/+!/!g;
1974 &$check_mount_path($mount_path);
1975 File
::Path
::mkpath
($mount_path);
1978 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1980 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1983 if (defined($mountpoint->{acl
})) {
1984 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
1986 my $readonly = $mountpoint->{ro
};
1988 my @extra_opts = ('-o', $optstring);
1992 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1993 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1995 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1996 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1998 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2000 if ($format eq 'subvol') {
2003 if ($scfg->{type
} eq 'zfspool') {
2004 my $path_arg = $path;
2005 $path_arg =~ s!^/+!!;
2006 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2008 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2011 bindmount
($path, $mount_path, $readonly, @extra_opts);
2012 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
2015 return wantarray ?
($path, 0, $mounted_dev) : $path;
2016 } elsif ($format eq 'raw' || $format eq 'iso') {
2020 if ($format eq 'iso') {
2021 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2022 } elsif ($isBase || defined($snapname)) {
2023 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2026 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
2028 push @extra_opts, '-o', 'ro' if $readonly;
2029 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2033 my $use_loopdev = 0;
2034 if ($scfg->{path
}) {
2035 $mounted_dev = run_with_loopdev
($domount, $path);
2037 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2038 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2039 $mounted_dev = $path;
2042 die "unsupported storage type '$scfg->{type}'\n";
2044 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
2046 die "unsupported image format '$format'\n";
2048 } elsif ($type eq 'device') {
2049 push @extra_opts, '-o', 'ro' if $readonly;
2050 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2051 return wantarray ?
($volid, 0, $volid) : $volid;
2052 } elsif ($type eq 'bind') {
2053 die "directory '$volid' does not exist\n" if ! -d
$volid;
2054 &$check_mount_path($volid);
2055 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
2056 warn "cannot enable quota control for bind mounts\n" if $quota;
2057 return wantarray ?
($volid, 0, undef) : $volid;
2060 die "unsupported storage";
2063 sub get_vm_volumes
{
2064 my ($conf, $excludes) = @_;
2068 foreach_mountpoint
($conf, sub {
2069 my ($ms, $mountpoint) = @_;
2071 return if $excludes && $ms eq $excludes;
2073 my $volid = $mountpoint->{volume
};
2075 return if !$volid || $mountpoint->{type
} ne 'volume';
2077 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2080 push @$vollist, $volid;
2087 my ($dev, $rootuid, $rootgid) = @_;
2089 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2090 '-E', "root_owner=$rootuid:$rootgid",
2095 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2097 if ($volid =~ m!^/dev/.+!) {
2102 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2104 die "cannot format volume '$volid' with no storage\n" if !$storage;
2106 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2108 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2110 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2111 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2113 die "cannot format volume '$volid' (format == $format)\n"
2114 if $format ne 'raw';
2116 mkfs
($path, $rootuid, $rootgid);
2120 my ($storecfg, $vollist) = @_;
2122 foreach my $volid (@$vollist) {
2123 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2129 my ($storecfg, $vmid, $settings, $conf) = @_;
2134 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2135 my $chown_vollist = [];
2137 foreach_mountpoint
($settings, sub {
2138 my ($ms, $mountpoint) = @_;
2140 my $volid = $mountpoint->{volume
};
2141 my $mp = $mountpoint->{mp
};
2143 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2145 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2146 my ($storeid, $size_gb) = ($1, $2);
2148 my $size_kb = int(${size_gb
}*1024) * 1024;
2150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2151 # fixme: use better naming ct-$vmid-disk-X.raw?
2153 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2155 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2157 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2159 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2161 push @$chown_vollist, $volid;
2163 } elsif ($scfg->{type
} eq 'zfspool') {
2165 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2167 push @$chown_vollist, $volid;
2168 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2170 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2171 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2173 } elsif ($scfg->{type
} eq 'rbd') {
2175 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2176 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2177 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2179 die "unable to create containers on storage type '$scfg->{type}'\n";
2181 push @$vollist, $volid;
2182 $mountpoint->{volume
} = $volid;
2183 $mountpoint->{size
} = $size_kb * 1024;
2184 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2186 # use specified/existing volid/dir/device
2187 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2191 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2192 foreach my $volid (@$chown_vollist) {
2193 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2194 chown($rootuid, $rootgid, $path);
2196 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2198 # free allocated images on error
2200 destroy_disks
($storecfg, $vollist);
2206 # bash completion helper
2208 sub complete_os_templates
{
2209 my ($cmdname, $pname, $cvalue) = @_;
2211 my $cfg = PVE
::Storage
::config
();
2215 if ($cvalue =~ m/^([^:]+):/) {
2219 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2220 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2223 foreach my $id (keys %$data) {
2224 foreach my $item (@{$data->{$id}}) {
2225 push @$res, $item->{volid
} if defined($item->{volid
});
2232 my $complete_ctid_full = sub {
2235 my $idlist = vmstatus
();
2237 my $active_hash = list_active_containers
();
2241 foreach my $id (keys %$idlist) {
2242 my $d = $idlist->{$id};
2243 if (defined($running)) {
2244 next if $d->{template
};
2245 next if $running && !$active_hash->{$id};
2246 next if !$running && $active_hash->{$id};
2255 return &$complete_ctid_full();
2258 sub complete_ctid_stopped
{
2259 return &$complete_ctid_full(0);
2262 sub complete_ctid_running
{
2263 return &$complete_ctid_full(1);
2273 my $lxc = $conf->{lxc
};
2274 foreach my $entry (@$lxc) {
2275 my ($key, $value) = @$entry;
2276 next if $key ne 'lxc.id_map';
2277 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2278 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2279 push @$id_map, [$type, $ct, $host, $length];
2281 $rootuid = $host if $type eq 'u';
2282 $rootgid = $host if $type eq 'g';
2285 die "failed to parse id_map: $value\n";
2289 if (!@$id_map && $conf->{unprivileged
}) {
2290 # Should we read them from /etc/subuid?
2291 $id_map = [ ['u', '0', '100000', '65536'],
2292 ['g', '0', '100000', '65536'] ];
2293 $rootuid = $rootgid = 100000;
2296 return ($id_map, $rootuid, $rootgid);
2299 sub userns_command
{
2302 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];