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 = PVE
::LXC
::Config-
>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 my $parse_ct_mountpoint_full = sub {
829 my ($desc, $data, $noerr) = @_;
834 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
836 return undef if $noerr;
840 if (defined(my $size = $res->{size
})) {
841 $size = PVE
::JSONSchema
::parse_size
($size);
842 if (!defined($size)) {
843 return undef if $noerr;
844 die "invalid size: $size\n";
846 $res->{size
} = $size;
849 $res->{type
} = PVE
::LXC
::Config-
>classify_mountpoint($res->{volume
});
854 sub parse_ct_rootfs
{
855 my ($data, $noerr) = @_;
857 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
859 $res->{mp
} = '/' if defined($res);
864 sub parse_ct_mountpoint
{
865 my ($data, $noerr) = @_;
867 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
870 sub print_ct_mountpoint
{
871 my ($info, $nomp) = @_;
872 my $skip = [ 'type' ];
873 push @$skip, 'mp' if $nomp;
874 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
877 sub print_lxc_network
{
879 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
882 sub parse_lxc_network
{
887 return $res if !$data;
889 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
891 $res->{type
} = 'veth';
892 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
897 sub read_cgroup_value
{
898 my ($group, $vmid, $name, $full) = @_;
900 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
902 return PVE
::Tools
::file_get_contents
($path) if $full;
904 return PVE
::Tools
::file_read_firstline
($path);
907 sub write_cgroup_value
{
908 my ($group, $vmid, $name, $value) = @_;
910 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
911 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
915 sub find_lxc_console_pids
{
919 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
922 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
925 my @args = split(/\0/, $cmdline);
927 # search for lxc-console -n <vmid>
928 return if scalar(@args) != 3;
929 return if $args[1] ne '-n';
930 return if $args[2] !~ m/^\d+$/;
931 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
935 push @{$res->{$vmid}}, $pid;
947 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
949 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
951 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
956 # Note: we cannot use Net:IP, because that only allows strict
958 sub parse_ipv4_cidr
{
959 my ($cidr, $noerr) = @_;
961 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
962 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
965 return undef if $noerr;
967 die "unable to parse ipv4 address/mask\n";
971 sub update_lxc_config
{
972 my ($storage_cfg, $vmid, $conf) = @_;
974 my $dir = "/var/lib/lxc/$vmid";
976 if ($conf->{template
}) {
978 unlink "$dir/config";
985 die "missing 'arch' - internal error" if !$conf->{arch
};
986 $raw .= "lxc.arch = $conf->{arch}\n";
988 my $unprivileged = $conf->{unprivileged
};
989 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
991 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
992 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) {
993 my $inc ="/usr/share/lxc/config/$ostype.common.conf";
994 $inc ="/usr/share/lxc/config/common.conf" if !-f
$inc;
995 $raw .= "lxc.include = $inc\n";
996 if ($unprivileged || $custom_idmap) {
997 $inc = "/usr/share/lxc/config/$ostype.userns.conf";
998 $inc = "/usr/share/lxc/config/userns.conf" if !-f
$inc;
999 $raw .= "lxc.include = $inc\n"
1002 die "implement me (ostype $ostype)";
1005 # WARNING: DO NOT REMOVE this without making sure that loop device nodes
1006 # cannot be exposed to the container with r/w access (cgroup perms).
1007 # When this is enabled mounts will still remain in the monitor's namespace
1008 # after the container unmounted them and thus will not detach from their
1009 # files while the container is running!
1010 $raw .= "lxc.monitor.unshare = 1\n";
1012 # Should we read them from /etc/subuid?
1013 if ($unprivileged && !$custom_idmap) {
1014 $raw .= "lxc.id_map = u 0 100000 65536\n";
1015 $raw .= "lxc.id_map = g 0 100000 65536\n";
1018 if (!PVE
::LXC
::Config-
>has_dev_console($conf)) {
1019 $raw .= "lxc.console = none\n";
1020 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1023 my $ttycount = get_tty_count
($conf);
1024 $raw .= "lxc.tty = $ttycount\n";
1026 # some init scripts expect a linux terminal (turnkey).
1027 $raw .= "lxc.environment = TERM=linux\n";
1029 my $utsname = $conf->{hostname
} || "CT$vmid";
1030 $raw .= "lxc.utsname = $utsname\n";
1032 my $memory = $conf->{memory
} || 512;
1033 my $swap = $conf->{swap
} // 0;
1035 my $lxcmem = int($memory*1024*1024);
1036 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1038 my $lxcswap = int(($memory + $swap)*1024*1024);
1039 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1041 if (my $cpulimit = $conf->{cpulimit
}) {
1042 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1043 my $value = int(100000*$cpulimit);
1044 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1047 my $shares = $conf->{cpuunits
} || 1024;
1048 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1050 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1052 $raw .= "lxc.rootfs = $dir/rootfs\n";
1055 foreach my $k (keys %$conf) {
1056 next if $k !~ m/^net(\d+)$/;
1058 my $d = parse_lxc_network
($conf->{$k});
1060 $raw .= "lxc.network.type = veth\n";
1061 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1062 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1063 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1064 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1067 if (my $lxcconf = $conf->{lxc
}) {
1068 foreach my $entry (@$lxcconf) {
1069 my ($k, $v) = @$entry;
1070 $netcount++ if $k eq 'lxc.network.type';
1071 $raw .= "$k = $v\n";
1075 $raw .= "lxc.network.type = empty\n" if !$netcount;
1077 File
::Path
::mkpath
("$dir/rootfs");
1079 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1082 # verify and cleanup nameserver list (replace \0 with ' ')
1083 sub verify_nameserver_list
{
1084 my ($nameserver_list) = @_;
1087 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1088 PVE
::JSONSchema
::pve_verify_ip
($server);
1089 push @list, $server;
1092 return join(' ', @list);
1095 sub verify_searchdomain_list
{
1096 my ($searchdomain_list) = @_;
1099 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1100 # todo: should we add checks for valid dns domains?
1101 push @list, $server;
1104 return join(' ', @list);
1107 sub update_pct_config
{
1108 my ($vmid, $conf, $running, $param, $delete) = @_;
1113 my @deleted_volumes;
1117 my $pid = find_lxc_pid
($vmid);
1118 $rootdir = "/proc/$pid/root";
1121 my $hotplug_error = sub {
1123 push @nohotplug, @_;
1130 if (defined($delete)) {
1131 foreach my $opt (@$delete) {
1132 if (!exists($conf->{$opt})) {
1133 warn "no such option: $opt\n";
1137 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1138 die "unable to delete required option '$opt'\n";
1139 } elsif ($opt eq 'swap') {
1140 delete $conf->{$opt};
1141 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1142 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1143 delete $conf->{$opt};
1144 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1145 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1146 next if $hotplug_error->($opt);
1147 delete $conf->{$opt};
1148 } elsif ($opt =~ m/^net(\d)$/) {
1149 delete $conf->{$opt};
1152 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1153 } elsif ($opt eq 'protection') {
1154 delete $conf->{$opt};
1155 } elsif ($opt =~ m/^unused(\d+)$/) {
1156 next if $hotplug_error->($opt);
1157 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1158 push @deleted_volumes, $conf->{$opt};
1159 delete $conf->{$opt};
1160 } elsif ($opt =~ m/^mp(\d+)$/) {
1161 next if $hotplug_error->($opt);
1162 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1163 my $mp = parse_ct_mountpoint
($conf->{$opt});
1164 delete $conf->{$opt};
1165 if ($mp->{type
} eq 'volume') {
1166 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1168 } elsif ($opt eq 'unprivileged') {
1169 die "unable to delete read-only option: '$opt'\n";
1171 die "implement me (delete: $opt)"
1173 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1177 # There's no separate swap size to configure, there's memory and "total"
1178 # memory (iow. memory+swap). This means we have to change them together.
1179 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1180 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1181 if (defined($wanted_memory) || defined($wanted_swap)) {
1183 my $old_memory = ($conf->{memory
} || 512);
1184 my $old_swap = ($conf->{swap
} || 0);
1186 $wanted_memory //= $old_memory;
1187 $wanted_swap //= $old_swap;
1189 my $total = $wanted_memory + $wanted_swap;
1191 my $old_total = $old_memory + $old_swap;
1192 if ($total > $old_total) {
1193 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1194 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1196 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1197 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1200 $conf->{memory
} = $wanted_memory;
1201 $conf->{swap
} = $wanted_swap;
1203 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1206 my $used_volids = {};
1208 foreach my $opt (keys %$param) {
1209 my $value = $param->{$opt};
1210 if ($opt eq 'hostname') {
1211 $conf->{$opt} = $value;
1212 } elsif ($opt eq 'onboot') {
1213 $conf->{$opt} = $value ?
1 : 0;
1214 } elsif ($opt eq 'startup') {
1215 $conf->{$opt} = $value;
1216 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1217 next if $hotplug_error->($opt);
1218 $conf->{$opt} = $value;
1219 } elsif ($opt eq 'nameserver') {
1220 next if $hotplug_error->($opt);
1221 my $list = verify_nameserver_list
($value);
1222 $conf->{$opt} = $list;
1223 } elsif ($opt eq 'searchdomain') {
1224 next if $hotplug_error->($opt);
1225 my $list = verify_searchdomain_list
($value);
1226 $conf->{$opt} = $list;
1227 } elsif ($opt eq 'cpulimit') {
1228 next if $hotplug_error->($opt); # FIXME: hotplug
1229 $conf->{$opt} = $value;
1230 } elsif ($opt eq 'cpuunits') {
1231 $conf->{$opt} = $value;
1232 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1233 } elsif ($opt eq 'description') {
1234 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1235 } elsif ($opt =~ m/^net(\d+)$/) {
1237 my $net = parse_lxc_network
($value);
1239 $conf->{$opt} = print_lxc_network
($net);
1241 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1243 } elsif ($opt eq 'protection') {
1244 $conf->{$opt} = $value ?
1 : 0;
1245 } elsif ($opt =~ m/^mp(\d+)$/) {
1246 next if $hotplug_error->($opt);
1247 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1248 my $old = $conf->{$opt};
1249 $conf->{$opt} = $value;
1250 if (defined($old)) {
1251 my $mp = parse_ct_mountpoint
($old);
1252 if ($mp->{type
} eq 'volume') {
1253 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1257 my $mp = parse_ct_mountpoint
($value);
1258 $used_volids->{$mp->{volume
}} = 1;
1259 } elsif ($opt eq 'rootfs') {
1260 next if $hotplug_error->($opt);
1261 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1262 my $old = $conf->{$opt};
1263 $conf->{$opt} = $value;
1264 if (defined($old)) {
1265 my $mp = parse_ct_rootfs
($old);
1266 if ($mp->{type
} eq 'volume') {
1267 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1270 my $mp = parse_ct_rootfs
($value);
1271 $used_volids->{$mp->{volume
}} = 1;
1272 } elsif ($opt eq 'unprivileged') {
1273 die "unable to modify read-only option: '$opt'\n";
1274 } elsif ($opt eq 'ostype') {
1275 next if $hotplug_error->($opt);
1276 $conf->{$opt} = $value;
1278 die "implement me: $opt";
1280 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1283 # Apply deletions and creations of new volumes
1284 if (@deleted_volumes) {
1285 my $storage_cfg = PVE
::Storage
::config
();
1286 foreach my $volume (@deleted_volumes) {
1287 next if $used_volids->{$volume}; # could have been re-added, too
1288 # also check for references in snapshots
1289 next if PVE
::LXC
::Config-
>is_volume_in_use($conf, $volume, 1);
1290 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1295 my $storage_cfg = PVE
::Storage
::config
();
1296 create_disks
($storage_cfg, $vmid, $conf, $conf);
1299 # This should be the last thing we do here
1300 if ($running && scalar(@nohotplug)) {
1301 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1308 return $conf->{tty
} // $confdesc->{tty
}->{default};
1314 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1317 sub get_console_command
{
1318 my ($vmid, $conf) = @_;
1320 my $cmode = get_cmode
($conf);
1322 if ($cmode eq 'console') {
1323 return ['lxc-console', '-n', $vmid, '-t', 0];
1324 } elsif ($cmode eq 'tty') {
1325 return ['lxc-console', '-n', $vmid];
1326 } elsif ($cmode eq 'shell') {
1327 return ['lxc-attach', '--clear-env', '-n', $vmid];
1329 die "internal error";
1333 sub get_primary_ips
{
1336 # return data from net0
1338 return undef if !defined($conf->{net0
});
1339 my $net = parse_lxc_network
($conf->{net0
});
1341 my $ipv4 = $net->{ip
};
1343 if ($ipv4 =~ /^(dhcp|manual)$/) {
1349 my $ipv6 = $net->{ip6
};
1351 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1358 return ($ipv4, $ipv6);
1361 sub delete_mountpoint_volume
{
1362 my ($storage_cfg, $vmid, $volume) = @_;
1364 return if PVE
::LXC
::Config-
>classify_mountpoint($volume) ne 'volume';
1366 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1367 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1370 sub destroy_lxc_container
{
1371 my ($storage_cfg, $vmid, $conf) = @_;
1373 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1374 my ($ms, $mountpoint) = @_;
1375 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1378 rmdir "/var/lib/lxc/$vmid/rootfs";
1379 unlink "/var/lib/lxc/$vmid/config";
1380 rmdir "/var/lib/lxc/$vmid";
1381 destroy_config
($vmid);
1383 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1384 #PVE::Tools::run_command($cmd);
1387 sub vm_stop_cleanup
{
1388 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1393 my $vollist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1394 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1397 warn $@ if $@; # avoid errors - just warn
1400 my $safe_num_ne = sub {
1403 return 0 if !defined($a) && !defined($b);
1404 return 1 if !defined($a);
1405 return 1 if !defined($b);
1410 my $safe_string_ne = sub {
1413 return 0 if !defined($a) && !defined($b);
1414 return 1 if !defined($a);
1415 return 1 if !defined($b);
1421 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1423 if ($newnet->{type
} ne 'veth') {
1424 # for when there are physical interfaces
1425 die "cannot update interface of type $newnet->{type}";
1428 my $veth = "veth${vmid}i${netid}";
1429 my $eth = $newnet->{name
};
1431 if (my $oldnetcfg = $conf->{$opt}) {
1432 my $oldnet = parse_lxc_network
($oldnetcfg);
1434 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1435 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1437 PVE
::Network
::veth_delete
($veth);
1438 delete $conf->{$opt};
1439 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1441 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1443 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1444 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1445 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1447 if ($oldnet->{bridge
}) {
1448 PVE
::Network
::tap_unplug
($veth);
1449 foreach (qw(bridge tag firewall)) {
1450 delete $oldnet->{$_};
1452 $conf->{$opt} = print_lxc_network
($oldnet);
1453 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1456 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1457 foreach (qw(bridge tag firewall)) {
1458 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1460 $conf->{$opt} = print_lxc_network
($oldnet);
1461 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1464 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1467 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1471 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1473 my $veth = "veth${vmid}i${netid}";
1474 my $vethpeer = $veth . "p";
1475 my $eth = $newnet->{name
};
1477 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1478 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1480 # attach peer in container
1481 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1482 PVE
::Tools
::run_command
($cmd);
1484 # link up peer in container
1485 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1486 PVE
::Tools
::run_command
($cmd);
1488 my $done = { type
=> 'veth' };
1489 foreach (qw(bridge tag firewall hwaddr name)) {
1490 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1492 $conf->{$opt} = print_lxc_network
($done);
1494 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1497 sub update_ipconfig
{
1498 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1500 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1502 my $optdata = parse_lxc_network
($conf->{$opt});
1506 my $cmdargs = shift;
1507 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1509 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1511 my $change_ip_config = sub {
1512 my ($ipversion) = @_;
1514 my $family_opt = "-$ipversion";
1515 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1516 my $gw= "gw$suffix";
1517 my $ip= "ip$suffix";
1519 my $newip = $newnet->{$ip};
1520 my $newgw = $newnet->{$gw};
1521 my $oldip = $optdata->{$ip};
1523 my $change_ip = &$safe_string_ne($oldip, $newip);
1524 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1526 return if !$change_ip && !$change_gw;
1528 # step 1: add new IP, if this fails we cancel
1529 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1530 if ($change_ip && $is_real_ip) {
1531 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1538 # step 2: replace gateway
1539 # If this fails we delete the added IP and cancel.
1540 # If it succeeds we save the config and delete the old IP, ignoring
1541 # errors. The config is then saved.
1542 # Note: 'ip route replace' can add
1546 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1547 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1549 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1553 # the route was not replaced, the old IP is still available
1554 # rollback (delete new IP) and cancel
1556 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1557 warn $@ if $@; # no need to die here
1562 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1563 # if the route was not deleted, the guest might have deleted it manually
1569 # from this point on we save the configuration
1570 # step 3: delete old IP ignoring errors
1571 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1572 # We need to enable promote_secondaries, otherwise our newly added
1573 # address will be removed along with the old one.
1576 if ($ipversion == 4) {
1577 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1578 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1579 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1581 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1583 warn $@ if $@; # no need to die here
1585 if ($ipversion == 4) {
1586 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1590 foreach my $property ($ip, $gw) {
1591 if ($newnet->{$property}) {
1592 $optdata->{$property} = $newnet->{$property};
1594 delete $optdata->{$property};
1597 $conf->{$opt} = print_lxc_network
($optdata);
1598 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1599 $lxc_setup->setup_network($conf);
1602 &$change_ip_config(4);
1603 &$change_ip_config(6);
1607 my $enter_namespace = sub {
1608 my ($vmid, $pid, $which, $type) = @_;
1609 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1610 or die "failed to open $which namespace of container $vmid: $!\n";
1611 PVE
::Tools
::setns
(fileno($fd), $type)
1612 or die "failed to enter $which namespace of container $vmid: $!\n";
1616 my $do_syncfs = sub {
1617 my ($vmid, $pid, $socket) = @_;
1619 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1621 # Tell the parent process to start reading our /proc/mounts
1622 print {$socket} "go\n";
1625 # Receive /proc/self/mounts
1626 my $mountdata = do { local $/ = undef; <$socket> };
1629 # Now sync all mountpoints...
1630 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1631 foreach my $mp (@$mounts) {
1632 my ($what, $dir, $fs) = @$mp;
1633 next if $fs eq 'fuse.lxcfs';
1634 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1639 sub sync_container_namespace
{
1641 my $pid = find_lxc_pid
($vmid);
1643 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1644 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1645 or die "failed to create socketpair: $!\n";
1648 die "fork failed: $!\n" if !defined($child);
1653 &$do_syncfs($vmid, $pid, $cfd);
1663 die "failed to enter container namespace\n" if $go ne "go\n";
1665 open my $mounts, '<', "/proc/$child/mounts"
1666 or die "failed to open container's /proc/mounts: $!\n";
1667 my $mountdata = do { local $/ = undef; <$mounts> };
1669 print {$pfd} $mountdata;
1672 while (waitpid($child, 0) != $child) {}
1673 die "failed to sync container namespace\n" if $? != 0;
1676 sub template_create
{
1677 my ($vmid, $conf) = @_;
1679 my $storecfg = PVE
::Storage
::config
();
1681 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1682 my $volid = $rootinfo->{volume
};
1684 die "Template feature is not available for '$volid'\n"
1685 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1687 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1689 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1690 $rootinfo->{volume
} = $template_volid;
1691 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1693 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1696 sub check_ct_modify_config_perm
{
1697 my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
1699 return 1 if $authuser eq 'root@pam';
1702 my ($opt, $delete) = @_;
1703 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1704 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1705 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1706 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1708 my $data = $opt eq 'rootfs' ? parse_ct_rootfs
($newconf->{$opt})
1709 : parse_ct_mountpoint
($newconf->{$opt});
1710 raise_perm_exc
("mountpoint type $data->{type}") if $data->{type
} ne 'volume';
1711 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1712 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1713 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1714 $opt eq 'searchdomain' || $opt eq 'hostname') {
1715 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1717 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1721 foreach my $opt (keys %$newconf) {
1724 foreach my $opt (@$delete) {
1732 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1734 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1735 my $volid_list = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1737 PVE
::LXC
::Config-
>foreach_mountpoint_reverse($conf, sub {
1738 my ($ms, $mountpoint) = @_;
1740 my $volid = $mountpoint->{volume
};
1741 my $mount = $mountpoint->{mp
};
1743 return if !$volid || !$mount;
1745 my $mount_path = "$rootdir/$mount";
1746 $mount_path =~ s!/+!/!g;
1748 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1751 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1764 my ($vmid, $storage_cfg, $conf) = @_;
1766 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1767 File
::Path
::make_path
($rootdir);
1769 my $volid_list = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1770 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1773 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1774 my ($ms, $mountpoint) = @_;
1776 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
1780 warn "mounting container failed\n";
1781 umount_all
($vmid, $storage_cfg, $conf, 1);
1789 sub mountpoint_mount_path
{
1790 my ($mountpoint, $storage_cfg, $snapname) = @_;
1792 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
1795 my $check_mount_path = sub {
1797 $path = File
::Spec-
>canonpath($path);
1798 my $real = Cwd
::realpath
($path);
1799 if ($real ne $path) {
1800 die "mount path modified by symlink: $path != $real";
1809 if ($line =~ m
@^(/dev/loop\d
+):@) {
1813 my $cmd = ['losetup', '--associated', $path];
1814 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
1818 # Run a function with a file attached to a loop device.
1819 # The loop device is always detached afterwards (or set to autoclear).
1820 # Returns the loop device.
1821 sub run_with_loopdev
{
1822 my ($func, $file) = @_;
1823 my $device = query_loopdev
($file);
1824 # Try to reuse an existing device
1826 # We assume that whoever setup the loop device is responsible for
1834 if ($line =~ m
@^(/dev/loop\d
+)$@) {
1838 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
1839 die "failed to setup loop device for $file\n" if !$device;
1840 eval { &$func($device); };
1842 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
1848 my ($dir, $dest, $ro, @extra_opts) = @_;
1849 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
1851 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
1853 warn "bindmount error\n";
1854 # don't leave writable bind-mounts behind...
1855 PVE
::Tools
::run_command
(['umount', $dest]);
1861 # use $rootdir = undef to just return the corresponding mount path
1862 sub mountpoint_mount
{
1863 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1865 my $volid = $mountpoint->{volume
};
1866 my $mount = $mountpoint->{mp
};
1867 my $type = $mountpoint->{type
};
1868 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
1871 return if !$volid || !$mount;
1875 if (defined($rootdir)) {
1876 $rootdir =~ s!/+$!!;
1877 $mount_path = "$rootdir/$mount";
1878 $mount_path =~ s!/+!/!g;
1879 &$check_mount_path($mount_path);
1880 File
::Path
::mkpath
($mount_path);
1883 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1885 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1888 if (defined($mountpoint->{acl
})) {
1889 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
1891 my $readonly = $mountpoint->{ro
};
1893 my @extra_opts = ('-o', $optstring);
1897 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1898 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1900 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1901 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1903 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
1905 if ($format eq 'subvol') {
1908 if ($scfg->{type
} eq 'zfspool') {
1909 my $path_arg = $path;
1910 $path_arg =~ s!^/+!!;
1911 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
1913 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1916 bindmount
($path, $mount_path, $readonly, @extra_opts);
1917 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
1920 return wantarray ?
($path, 0, $mounted_dev) : $path;
1921 } elsif ($format eq 'raw' || $format eq 'iso') {
1925 if ($format eq 'iso') {
1926 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
1927 } elsif ($isBase || defined($snapname)) {
1928 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
1931 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
1933 push @extra_opts, '-o', 'ro' if $readonly;
1934 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
1938 my $use_loopdev = 0;
1939 if ($scfg->{path
}) {
1940 $mounted_dev = run_with_loopdev
($domount, $path);
1942 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
1943 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
1944 $mounted_dev = $path;
1947 die "unsupported storage type '$scfg->{type}'\n";
1949 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
1951 die "unsupported image format '$format'\n";
1953 } elsif ($type eq 'device') {
1954 push @extra_opts, '-o', 'ro' if $readonly;
1955 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
1956 return wantarray ?
($volid, 0, $volid) : $volid;
1957 } elsif ($type eq 'bind') {
1958 die "directory '$volid' does not exist\n" if ! -d
$volid;
1959 &$check_mount_path($volid);
1960 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
1961 warn "cannot enable quota control for bind mounts\n" if $quota;
1962 return wantarray ?
($volid, 0, undef) : $volid;
1965 die "unsupported storage";
1969 my ($dev, $rootuid, $rootgid) = @_;
1971 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
1972 '-E', "root_owner=$rootuid:$rootgid",
1977 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
1979 if ($volid =~ m!^/dev/.+!) {
1984 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1986 die "cannot format volume '$volid' with no storage\n" if !$storage;
1988 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1990 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1992 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1993 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1995 die "cannot format volume '$volid' (format == $format)\n"
1996 if $format ne 'raw';
1998 mkfs
($path, $rootuid, $rootgid);
2002 my ($storecfg, $vollist) = @_;
2004 foreach my $volid (@$vollist) {
2005 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2011 my ($storecfg, $vmid, $settings, $conf) = @_;
2016 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2017 my $chown_vollist = [];
2019 PVE
::LXC
::Config-
>foreach_mountpoint($settings, sub {
2020 my ($ms, $mountpoint) = @_;
2022 my $volid = $mountpoint->{volume
};
2023 my $mp = $mountpoint->{mp
};
2025 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2027 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2028 my ($storeid, $size_gb) = ($1, $2);
2030 my $size_kb = int(${size_gb
}*1024) * 1024;
2032 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2033 # fixme: use better naming ct-$vmid-disk-X.raw?
2035 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2037 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2039 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2041 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2043 push @$chown_vollist, $volid;
2045 } elsif ($scfg->{type
} eq 'zfspool') {
2047 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2049 push @$chown_vollist, $volid;
2050 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2052 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2053 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2055 } elsif ($scfg->{type
} eq 'rbd') {
2057 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2058 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2059 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2061 die "unable to create containers on storage type '$scfg->{type}'\n";
2063 push @$vollist, $volid;
2064 $mountpoint->{volume
} = $volid;
2065 $mountpoint->{size
} = $size_kb * 1024;
2066 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2068 # use specified/existing volid/dir/device
2069 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2073 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2074 foreach my $volid (@$chown_vollist) {
2075 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2076 chown($rootuid, $rootgid, $path);
2078 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2080 # free allocated images on error
2082 destroy_disks
($storecfg, $vollist);
2088 # bash completion helper
2090 sub complete_os_templates
{
2091 my ($cmdname, $pname, $cvalue) = @_;
2093 my $cfg = PVE
::Storage
::config
();
2097 if ($cvalue =~ m/^([^:]+):/) {
2101 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2102 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2105 foreach my $id (keys %$data) {
2106 foreach my $item (@{$data->{$id}}) {
2107 push @$res, $item->{volid
} if defined($item->{volid
});
2114 my $complete_ctid_full = sub {
2117 my $idlist = vmstatus
();
2119 my $active_hash = list_active_containers
();
2123 foreach my $id (keys %$idlist) {
2124 my $d = $idlist->{$id};
2125 if (defined($running)) {
2126 next if $d->{template
};
2127 next if $running && !$active_hash->{$id};
2128 next if !$running && $active_hash->{$id};
2137 return &$complete_ctid_full();
2140 sub complete_ctid_stopped
{
2141 return &$complete_ctid_full(0);
2144 sub complete_ctid_running
{
2145 return &$complete_ctid_full(1);
2155 my $lxc = $conf->{lxc
};
2156 foreach my $entry (@$lxc) {
2157 my ($key, $value) = @$entry;
2158 next if $key ne 'lxc.id_map';
2159 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2160 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2161 push @$id_map, [$type, $ct, $host, $length];
2163 $rootuid = $host if $type eq 'u';
2164 $rootgid = $host if $type eq 'g';
2167 die "failed to parse id_map: $value\n";
2171 if (!@$id_map && $conf->{unprivileged
}) {
2172 # Should we read them from /etc/subuid?
2173 $id_map = [ ['u', '0', '100000', '65536'],
2174 ['g', '0', '100000', '65536'] ];
2175 $rootuid = $rootgid = 100000;
2178 return ($id_map, $rootuid, $rootgid);
2181 sub userns_command
{
2184 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];