12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
24 my $nodename = PVE
::INotify
::nodename
();
26 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
32 format_description
=> 'volume',
33 description
=> 'Volume, device or directory to mount into the container.',
37 format_description
=> '[1|0]',
38 description
=> 'Whether to include the mountpoint in backups.',
43 format_description
=> 'DiskSize',
44 pattern
=> '\d+[TGMK]?',
45 description
=> 'Volume size (read only value).',
50 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
51 type
=> 'string', format
=> $rootfs_desc,
52 description
=> "Use volume as container root.",
56 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
57 description
=> "The name of the snapshot.",
58 type
=> 'string', format
=> 'pve-configid',
66 description
=> "Lock/unlock the VM.",
67 enum
=> [qw(migrate backup snapshot rollback)],
72 description
=> "Specifies whether a VM will be started during system bootup.",
75 startup
=> get_standard_option
('pve-startup-order'),
79 description
=> "Enable/disable Template.",
85 enum
=> ['amd64', 'i386'],
86 description
=> "OS architecture type.",
92 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
93 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
98 description
=> "Attach a console device (/dev/console) to the container.",
104 description
=> "Specify the number of tty available to the container",
112 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
120 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 weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
128 description
=> "Amount of RAM for the VM in MB.",
135 description
=> "Amount of SWAP for the VM in MB.",
141 description
=> "Set a host name for the container.",
142 type
=> 'string', format
=> 'dns-name',
148 description
=> "Container description. Only used on the configuration web interface.",
152 type
=> 'string', format
=> 'dns-name-list',
153 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
157 type
=> 'string', format
=> 'address-list',
158 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
160 rootfs
=> get_standard_option
('pve-ct-rootfs'),
163 type
=> 'string', format
=> 'pve-configid',
165 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
169 description
=> "Timestamp for snapshots.",
175 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).",
177 enum
=> ['shell', 'console', 'tty'],
183 description
=> "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
188 my $valid_lxc_conf_keys = {
192 'lxc.haltsignal' => 1,
193 'lxc.rebootsignal' => 1,
194 'lxc.stopsignal' => 1,
196 'lxc.network.type' => 1,
197 'lxc.network.flags' => 1,
198 'lxc.network.link' => 1,
199 'lxc.network.mtu' => 1,
200 'lxc.network.name' => 1,
201 'lxc.network.hwaddr' => 1,
202 'lxc.network.ipv4' => 1,
203 'lxc.network.ipv4.gateway' => 1,
204 'lxc.network.ipv6' => 1,
205 'lxc.network.ipv6.gateway' => 1,
206 'lxc.network.script.up' => 1,
207 'lxc.network.script.down' => 1,
209 'lxc.console.logfile' => 1,
212 'lxc.devttydir' => 1,
213 'lxc.hook.autodev' => 1,
217 'lxc.mount.entry' => 1,
218 'lxc.mount.auto' => 1,
220 'lxc.rootfs.mount' => 1,
221 'lxc.rootfs.options' => 1,
225 'lxc.aa_profile' => 1,
226 'lxc.aa_allow_incomplete' => 1,
227 'lxc.se_context' => 1,
230 'lxc.hook.pre-start' => 1,
231 'lxc.hook.pre-mount' => 1,
232 'lxc.hook.mount' => 1,
233 'lxc.hook.start' => 1,
234 'lxc.hook.post-stop' => 1,
235 'lxc.hook.clone' => 1,
236 'lxc.hook.destroy' => 1,
239 'lxc.start.auto' => 1,
240 'lxc.start.delay' => 1,
241 'lxc.start.order' => 1,
243 'lxc.environment' => 1,
254 description
=> "Network interface type.",
259 format_description
=> 'String',
260 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
261 pattern
=> '[-_.\w\d]+',
265 format_description
=> 'vmbr<Number>',
266 description
=> 'Bridge to attach the network device to.',
267 pattern
=> '[-_.\w\d]+',
271 format_description
=> 'MAC',
272 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
273 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
278 format_description
=> 'Number',
279 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
284 format
=> 'pve-ipv4-config',
285 format_description
=> 'IPv4Format/CIDR',
286 description
=> 'IPv4 address in CIDR format.',
292 format_description
=> 'GatewayIPv4',
293 description
=> 'Default gateway for IPv4 traffic.',
298 format
=> 'pve-ipv6-config',
299 format_description
=> 'IPv6Format/CIDR',
300 description
=> 'IPv6 address in CIDR format.',
306 format_description
=> 'GatewayIPv6',
307 description
=> 'Default gateway for IPv6 traffic.',
312 format_description
=> '[1|0]',
313 description
=> "Controls whether this interface's firewall rules should be used.",
318 format_description
=> 'VlanNo',
321 description
=> "VLAN tag foro this interface.",
325 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
327 my $MAX_LXC_NETWORKS = 10;
328 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
329 $confdesc->{"net$i"} = {
331 type
=> 'string', format
=> $netconf_desc,
332 description
=> "Specifies network interfaces for the container.",
340 format_description
=> 'Path',
341 description
=> 'Path to the mountpoint as seen from inside the container.',
345 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
347 my $MAX_MOUNT_POINTS = 10;
348 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
349 $confdesc->{"mp$i"} = {
351 type
=> 'string', format
=> $mp_desc,
352 description
=> "Use volume as container mount point (experimental feature).",
357 sub write_pct_config
{
358 my ($filename, $conf) = @_;
360 delete $conf->{snapstate
}; # just to be sure
362 my $generate_raw_config = sub {
367 # add description as comment to top of file
368 my $descr = $conf->{description
} || '';
369 foreach my $cl (split(/\n/, $descr)) {
370 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
373 foreach my $key (sort keys %$conf) {
374 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
375 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
376 my $value = $conf->{$key};
377 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
378 $raw .= "$key: $value\n";
381 if (my $lxcconf = $conf->{lxc
}) {
382 foreach my $entry (@$lxcconf) {
383 my ($k, $v) = @$entry;
391 my $raw = &$generate_raw_config($conf);
393 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
394 $raw .= "\n[$snapname]\n";
395 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
402 my ($key, $value) = @_;
404 die "unknown setting '$key'\n" if !$confdesc->{$key};
406 my $type = $confdesc->{$key}->{type
};
408 if (!defined($value)) {
409 die "got undefined value\n";
412 if ($value =~ m/[\n\r]/) {
413 die "property contains a line feed\n";
416 if ($type eq 'boolean') {
417 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
418 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
419 die "type check ('boolean') failed - got '$value'\n";
420 } elsif ($type eq 'integer') {
421 return int($1) if $value =~ m/^(\d+)$/;
422 die "type check ('integer') failed - got '$value'\n";
423 } elsif ($type eq 'number') {
424 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
425 die "type check ('number') failed - got '$value'\n";
426 } elsif ($type eq 'string') {
427 if (my $fmt = $confdesc->{$key}->{format
}) {
428 PVE
::JSONSchema
::check_format
($fmt, $value);
437 sub parse_pct_config
{
438 my ($filename, $raw) = @_;
440 return undef if !defined($raw);
443 digest
=> Digest
::SHA
::sha1_hex
($raw),
447 $filename =~ m
|/lxc/(\d
+).conf
$|
448 || die "got strange filename '$filename'";
456 my @lines = split(/\n/, $raw);
457 foreach my $line (@lines) {
458 next if $line =~ m/^\s*$/;
460 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
462 $conf->{description
} = $descr if $descr;
464 $conf = $res->{snapshots
}->{$section} = {};
468 if ($line =~ m/^\#(.*)\s*$/) {
469 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
473 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
476 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
477 push @{$conf->{lxc
}}, [$key, $value];
479 warn "vm $vmid - unable to parse config: $line\n";
481 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
482 $descr .= PVE
::Tools
::decode_text
($2);
483 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
484 $conf->{snapstate
} = $1;
485 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
488 eval { $value = check_type
($key, $value); };
489 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
490 $conf->{$key} = $value;
492 warn "vm $vmid - unable to parse config: $line\n";
496 $conf->{description
} = $descr if $descr;
498 delete $res->{snapstate
}; # just to be sure
504 my $vmlist = PVE
::Cluster
::get_vmlist
();
506 return $res if !$vmlist || !$vmlist->{ids
};
507 my $ids = $vmlist->{ids
};
509 foreach my $vmid (keys %$ids) {
510 next if !$vmid; # skip CT0
511 my $d = $ids->{$vmid};
512 next if !$d->{node
} || $d->{node
} ne $nodename;
513 next if !$d->{type
} || $d->{type
} ne 'lxc';
514 $res->{$vmid}->{type
} = 'lxc';
519 sub cfs_config_path
{
520 my ($vmid, $node) = @_;
522 $node = $nodename if !$node;
523 return "nodes/$node/lxc/$vmid.conf";
527 my ($vmid, $node) = @_;
529 my $cfspath = cfs_config_path
($vmid, $node);
530 return "/etc/pve/$cfspath";
534 my ($vmid, $node) = @_;
536 $node = $nodename if !$node;
537 my $cfspath = cfs_config_path
($vmid, $node);
539 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
540 die "container $vmid does not exists\n" if !defined($conf);
546 my ($vmid, $conf) = @_;
548 my $dir = "/etc/pve/nodes/$nodename/lxc";
551 write_config
($vmid, $conf);
557 unlink config_file
($vmid, $nodename);
561 my ($vmid, $conf) = @_;
563 my $cfspath = cfs_config_path
($vmid);
565 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
568 # flock: we use one file handle per process, so lock file
569 # can be called multiple times and succeeds for the same process.
571 my $lock_handles = {};
572 my $lockdir = "/run/lock/lxc";
577 return "$lockdir/pve-config-${vmid}.lock";
581 my ($vmid, $timeout) = @_;
583 $timeout = 10 if !$timeout;
586 my $filename = lock_filename
($vmid);
588 mkdir $lockdir if !-d
$lockdir;
590 my $lock_func = sub {
591 if (!$lock_handles->{$$}->{$filename}) {
592 my $fh = new IO
::File
(">>$filename") ||
593 die "can't open file - $!\n";
594 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
597 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
598 print STDERR
"trying to aquire lock...";
601 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
602 # try again on EINTR (see bug #273)
603 if ($success || ($! != EINTR
)) {
608 print STDERR
" failed\n";
609 die "can't aquire lock - $!\n";
612 print STDERR
" OK\n";
615 $lock_handles->{$$}->{$filename}->{refcount
}++;
618 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
621 die "can't lock file '$filename' - $err";
628 my $filename = lock_filename
($vmid);
630 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
631 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
632 if ($refcount <= 0) {
633 $lock_handles->{$$}->{$filename} = undef;
640 my ($vmid, $timeout, $code, @param) = @_;
644 lock_aquire
($vmid, $timeout);
645 eval { $res = &$code(@param) };
657 return defined($confdesc->{$name});
660 # add JSON properties for create and set function
661 sub json_config_properties
{
664 foreach my $opt (keys %$confdesc) {
665 next if $opt eq 'parent' || $opt eq 'snaptime';
666 next if $prop->{$opt};
667 $prop->{$opt} = $confdesc->{$opt};
673 sub json_config_properties_no_rootfs
{
676 foreach my $opt (keys %$confdesc) {
677 next if $prop->{$opt};
678 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
679 $prop->{$opt} = $confdesc->{$opt};
685 # container status helpers
687 sub list_active_containers
{
689 my $filename = "/proc/net/unix";
691 # similar test is used by lcxcontainers.c: list_active_containers
694 my $fh = IO
::File-
>new ($filename, "r");
697 while (defined(my $line = <$fh>)) {
698 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
700 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
711 # warning: this is slow
715 my $active_hash = list_active_containers
();
717 return 1 if defined($active_hash->{$vmid});
722 sub get_container_disk_usage
{
725 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
735 if (my ($fsid, $total, $used, $avail) = $line =~
736 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
744 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
753 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
755 my $active_hash = list_active_containers
();
757 foreach my $vmid (keys %$list) {
758 my $d = $list->{$vmid};
760 my $running = defined($active_hash->{$vmid});
762 $d->{status
} = $running ?
'running' : 'stopped';
764 my $cfspath = cfs_config_path
($vmid);
765 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
767 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
768 $d->{name
} =~ s/[\s]//g;
770 $d->{cpus
} = $conf->{cpulimit
} // 0;
773 my $res = get_container_disk_usage
($vmid);
774 $d->{disk
} = $res->{used
};
775 $d->{maxdisk
} = $res->{total
};
778 # use 4GB by default ??
779 if (my $rootfs = $conf->{rootfs
}) {
780 my $rootinfo = parse_ct_mountpoint
($rootfs);
781 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
783 $d->{maxdisk
} = 4*1024*1024*1024;
789 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
790 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
801 $d->{template
} = is_template
($conf);
804 foreach my $vmid (keys %$list) {
805 my $d = $list->{$vmid};
806 next if $d->{status
} ne 'running';
808 $d->{uptime
} = 100; # fixme:
810 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
811 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
813 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
814 my @bytes = split(/\n/, $blkio_bytes);
815 foreach my $byte (@bytes) {
816 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
817 $d->{diskread
} = $2 if $key eq 'Read';
818 $d->{diskwrite
} = $2 if $key eq 'Write';
826 my $parse_size = sub {
829 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
830 my ($size, $unit) = ($1, $3);
833 $size = $size * 1024;
834 } elsif ($unit eq 'M') {
835 $size = $size * 1024 * 1024;
836 } elsif ($unit eq 'G') {
837 $size = $size * 1024 * 1024 * 1024;
843 my $format_size = sub {
848 my $kb = int($size/1024);
849 return $size if $kb*1024 != $size;
851 my $mb = int($kb/1024);
852 return "${kb}K" if $mb*1024 != $kb;
854 my $gb = int($mb/1024);
855 return "${mb}M" if $gb*1024 != $mb;
860 sub parse_ct_mountpoint
{
866 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
872 return undef if !defined($res->{volume
});
875 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
881 sub print_ct_mountpoint
{
882 my ($info, $nomp) = @_;
886 die "missing volume\n" if !$info->{volume
};
888 foreach my $o (qw(backup)) {
889 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
893 $opts .= ",size=" . &$format_size($info->{size
});
896 $opts .= ",mp=$info->{mp}" if !$nomp;
898 return "$info->{volume}$opts";
901 sub print_lxc_network
{
904 die "no network name defined\n" if !$net->{name
};
906 my $res = "name=$net->{name}";
908 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
909 next if !defined($net->{$k});
910 $res .= ",$k=$net->{$k}";
916 sub parse_lxc_network
{
921 return $res if !$data;
923 eval { $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data) };
929 $res->{type
} = 'veth';
930 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
935 sub read_cgroup_value
{
936 my ($group, $vmid, $name, $full) = @_;
938 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
940 return PVE
::Tools
::file_get_contents
($path) if $full;
942 return PVE
::Tools
::file_read_firstline
($path);
945 sub write_cgroup_value
{
946 my ($group, $vmid, $name, $value) = @_;
948 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
949 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
953 sub find_lxc_console_pids
{
957 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
960 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
963 my @args = split(/\0/, $cmdline);
965 # serach for lxc-console -n <vmid>
966 return if scalar(@args) != 3;
967 return if $args[1] ne '-n';
968 return if $args[2] !~ m/^\d+$/;
969 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
973 push @{$res->{$vmid}}, $pid;
985 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
987 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
989 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
994 my $ipv4_reverse_mask = [
1030 # Note: we cannot use Net:IP, because that only allows strict
1032 sub parse_ipv4_cidr
{
1033 my ($cidr, $noerr) = @_;
1035 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1036 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
1039 return undef if $noerr;
1041 die "unable to parse ipv4 address/mask\n";
1047 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1050 sub check_protection
{
1051 my ($vm_conf, $err_msg) = @_;
1053 if ($vm_conf->{protection
}) {
1054 die "$err_msg - protection mode enabled\n";
1058 sub update_lxc_config
{
1059 my ($storage_cfg, $vmid, $conf) = @_;
1061 my $dir = "/var/lib/lxc/$vmid";
1063 if ($conf->{template
}) {
1065 unlink "$dir/config";
1072 die "missing 'arch' - internal error" if !$conf->{arch
};
1073 $raw .= "lxc.arch = $conf->{arch}\n";
1075 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1076 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1077 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1082 if (!has_dev_console
($conf)) {
1083 $raw .= "lxc.console = none\n";
1084 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1087 my $ttycount = get_tty_count
($conf);
1088 $raw .= "lxc.tty = $ttycount\n";
1090 # some init scripts expects a linux terminal (turnkey).
1091 $raw .= "lxc.environment = TERM=linux\n";
1093 my $utsname = $conf->{hostname
} || "CT$vmid";
1094 $raw .= "lxc.utsname = $utsname\n";
1096 my $memory = $conf->{memory
} || 512;
1097 my $swap = $conf->{swap
} // 0;
1099 my $lxcmem = int($memory*1024*1024);
1100 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1102 my $lxcswap = int(($memory + $swap)*1024*1024);
1103 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1105 if (my $cpulimit = $conf->{cpulimit
}) {
1106 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1107 my $value = int(100000*$cpulimit);
1108 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1111 my $shares = $conf->{cpuunits
} || 1024;
1112 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1114 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1115 $mountpoint->{mp
} = '/';
1117 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1118 $path = "loop:$path" if $use_loopdev;
1120 $raw .= "lxc.rootfs = $path\n";
1123 foreach my $k (keys %$conf) {
1124 next if $k !~ m/^net(\d+)$/;
1126 my $d = parse_lxc_network
($conf->{$k});
1128 $raw .= "lxc.network.type = veth\n";
1129 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1130 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1131 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1132 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1135 if (my $lxcconf = $conf->{lxc
}) {
1136 foreach my $entry (@$lxcconf) {
1137 my ($k, $v) = @$entry;
1138 $netcount++ if $k eq 'lxc.network.type';
1139 $raw .= "$k = $v\n";
1143 $raw .= "lxc.network.type = empty\n" if !$netcount;
1145 File
::Path
::mkpath
("$dir/rootfs");
1147 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1150 # verify and cleanup nameserver list (replace \0 with ' ')
1151 sub verify_nameserver_list
{
1152 my ($nameserver_list) = @_;
1155 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1156 PVE
::JSONSchema
::pve_verify_ip
($server);
1157 push @list, $server;
1160 return join(' ', @list);
1163 sub verify_searchdomain_list
{
1164 my ($searchdomain_list) = @_;
1167 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1168 # todo: should we add checks for valid dns domains?
1169 push @list, $server;
1172 return join(' ', @list);
1175 sub update_pct_config
{
1176 my ($vmid, $conf, $running, $param, $delete) = @_;
1184 my $pid = find_lxc_pid
($vmid);
1185 $rootdir = "/proc/$pid/root";
1188 if (defined($delete)) {
1189 foreach my $opt (@$delete) {
1190 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1191 die "unable to delete required option '$opt'\n";
1192 } elsif ($opt eq 'swap') {
1193 delete $conf->{$opt};
1194 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1195 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1196 delete $conf->{$opt};
1197 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1198 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1199 delete $conf->{$opt};
1200 push @nohotplug, $opt;
1202 } elsif ($opt =~ m/^net(\d)$/) {
1203 delete $conf->{$opt};
1206 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1207 } elsif ($opt eq 'protection') {
1208 delete $conf->{$opt};
1209 } elsif ($opt =~ m/^mp(\d+)$/) {
1210 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1211 delete $conf->{$opt};
1212 push @nohotplug, $opt;
1217 write_config
($vmid, $conf) if $running;
1221 # There's no separate swap size to configure, there's memory and "total"
1222 # memory (iow. memory+swap). This means we have to change them together.
1223 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1224 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1225 if (defined($wanted_memory) || defined($wanted_swap)) {
1227 $wanted_memory //= ($conf->{memory
} || 512);
1228 $wanted_swap //= ($conf->{swap
} || 0);
1230 my $total = $wanted_memory + $wanted_swap;
1232 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1233 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1235 $conf->{memory
} = $wanted_memory;
1236 $conf->{swap
} = $wanted_swap;
1238 write_config
($vmid, $conf) if $running;
1241 foreach my $opt (keys %$param) {
1242 my $value = $param->{$opt};
1243 if ($opt eq 'hostname') {
1244 $conf->{$opt} = $value;
1245 } elsif ($opt eq 'onboot') {
1246 $conf->{$opt} = $value ?
1 : 0;
1247 } elsif ($opt eq 'startup') {
1248 $conf->{$opt} = $value;
1249 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1250 $conf->{$opt} = $value;
1251 push @nohotplug, $opt;
1253 } elsif ($opt eq 'nameserver') {
1254 my $list = verify_nameserver_list
($value);
1255 $conf->{$opt} = $list;
1256 push @nohotplug, $opt;
1258 } elsif ($opt eq 'searchdomain') {
1259 my $list = verify_searchdomain_list
($value);
1260 $conf->{$opt} = $list;
1261 push @nohotplug, $opt;
1263 } elsif ($opt eq 'cpulimit') {
1264 $conf->{$opt} = $value;
1265 push @nohotplug, $opt; # fixme: hotplug
1267 } elsif ($opt eq 'cpuunits') {
1268 $conf->{$opt} = $value;
1269 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1270 } elsif ($opt eq 'description') {
1271 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1272 } elsif ($opt =~ m/^net(\d+)$/) {
1274 my $net = parse_lxc_network
($value);
1276 $conf->{$opt} = print_lxc_network
($net);
1278 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1280 } elsif ($opt eq 'protection') {
1281 $conf->{$opt} = $value ?
1 : 0;
1282 } elsif ($opt =~ m/^mp(\d+)$/) {
1283 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1284 $conf->{$opt} = $value;
1286 push @nohotplug, $opt;
1288 } elsif ($opt eq 'rootfs') {
1289 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1290 die "implement me: $opt";
1292 die "implement me: $opt";
1294 write_config
($vmid, $conf) if $running;
1297 if ($running && scalar(@nohotplug)) {
1298 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1302 my $storage_cfg = PVE
::Storage
::config
();
1303 create_disks
($storage_cfg, $vmid, $conf, $conf);
1307 sub has_dev_console
{
1310 return !(defined($conf->{console
}) && !$conf->{console
});
1316 return $conf->{tty
} // $confdesc->{tty
}->{default};
1322 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1325 sub get_console_command
{
1326 my ($vmid, $conf) = @_;
1328 my $cmode = get_cmode
($conf);
1330 if ($cmode eq 'console') {
1331 return ['lxc-console', '-n', $vmid, '-t', 0];
1332 } elsif ($cmode eq 'tty') {
1333 return ['lxc-console', '-n', $vmid];
1334 } elsif ($cmode eq 'shell') {
1335 return ['lxc-attach', '--clear-env', '-n', $vmid];
1337 die "internal error";
1341 sub get_primary_ips
{
1344 # return data from net0
1346 return undef if !defined($conf->{net0
});
1347 my $net = parse_lxc_network
($conf->{net0
});
1349 my $ipv4 = $net->{ip
};
1351 if ($ipv4 =~ /^(dhcp|manual)$/) {
1357 my $ipv6 = $net->{ip6
};
1359 if ($ipv6 =~ /^(dhcp|manual)$/) {
1366 return ($ipv4, $ipv6);
1370 sub destroy_lxc_container
{
1371 my ($storage_cfg, $vmid, $conf) = @_;
1373 foreach_mountpoint
($conf, sub {
1374 my ($ms, $mountpoint) = @_;
1376 # skip bind mounts and block devices
1377 if ($mountpoint->{volume
} =~ m
|^/|) {
1381 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1382 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1385 rmdir "/var/lib/lxc/$vmid/rootfs";
1386 unlink "/var/lib/lxc/$vmid/config";
1387 rmdir "/var/lib/lxc/$vmid";
1388 destroy_config
($vmid);
1390 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1391 #PVE::Tools::run_command($cmd);
1394 sub vm_stop_cleanup
{
1395 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1400 my $vollist = get_vm_volumes
($conf);
1401 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1404 warn $@ if $@; # avoid errors - just warn
1407 my $safe_num_ne = sub {
1410 return 0 if !defined($a) && !defined($b);
1411 return 1 if !defined($a);
1412 return 1 if !defined($b);
1417 my $safe_string_ne = sub {
1420 return 0 if !defined($a) && !defined($b);
1421 return 1 if !defined($a);
1422 return 1 if !defined($b);
1428 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1430 if ($newnet->{type
} ne 'veth') {
1431 # for when there are physical interfaces
1432 die "cannot update interface of type $newnet->{type}";
1435 my $veth = "veth${vmid}i${netid}";
1436 my $eth = $newnet->{name
};
1438 if (my $oldnetcfg = $conf->{$opt}) {
1439 my $oldnet = parse_lxc_network
($oldnetcfg);
1441 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1442 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1444 PVE
::Network
::veth_delete
($veth);
1445 delete $conf->{$opt};
1446 write_config
($vmid, $conf);
1448 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1450 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1451 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1452 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1454 if ($oldnet->{bridge
}) {
1455 PVE
::Network
::tap_unplug
($veth);
1456 foreach (qw(bridge tag firewall)) {
1457 delete $oldnet->{$_};
1459 $conf->{$opt} = print_lxc_network
($oldnet);
1460 write_config
($vmid, $conf);
1463 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1464 foreach (qw(bridge tag firewall)) {
1465 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1467 $conf->{$opt} = print_lxc_network
($oldnet);
1468 write_config
($vmid, $conf);
1471 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1474 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1478 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1480 my $veth = "veth${vmid}i${netid}";
1481 my $vethpeer = $veth . "p";
1482 my $eth = $newnet->{name
};
1484 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1485 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1487 # attach peer in container
1488 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1489 PVE
::Tools
::run_command
($cmd);
1491 # link up peer in container
1492 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1493 PVE
::Tools
::run_command
($cmd);
1495 my $done = { type
=> 'veth' };
1496 foreach (qw(bridge tag firewall hwaddr name)) {
1497 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1499 $conf->{$opt} = print_lxc_network
($done);
1501 write_config
($vmid, $conf);
1504 sub update_ipconfig
{
1505 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1507 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1509 my $optdata = parse_lxc_network
($conf->{$opt});
1513 my $cmdargs = shift;
1514 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1516 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1518 my $change_ip_config = sub {
1519 my ($ipversion) = @_;
1521 my $family_opt = "-$ipversion";
1522 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1523 my $gw= "gw$suffix";
1524 my $ip= "ip$suffix";
1526 my $newip = $newnet->{$ip};
1527 my $newgw = $newnet->{$gw};
1528 my $oldip = $optdata->{$ip};
1530 my $change_ip = &$safe_string_ne($oldip, $newip);
1531 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1533 return if !$change_ip && !$change_gw;
1535 # step 1: add new IP, if this fails we cancel
1536 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1537 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1544 # step 2: replace gateway
1545 # If this fails we delete the added IP and cancel.
1546 # If it succeeds we save the config and delete the old IP, ignoring
1547 # errors. The config is then saved.
1548 # Note: 'ip route replace' can add
1551 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1554 # the route was not replaced, the old IP is still available
1555 # rollback (delete new IP) and cancel
1557 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1558 warn $@ if $@; # no need to die here
1563 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1564 # if the route was not deleted, the guest might have deleted it manually
1570 # from this point on we save the configuration
1571 # step 3: delete old IP ignoring errors
1572 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1573 # We need to enable promote_secondaries, otherwise our newly added
1574 # address will be removed along with the old one.
1577 if ($ipversion == 4) {
1578 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1579 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1580 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1582 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1584 warn $@ if $@; # no need to die here
1586 if ($ipversion == 4) {
1587 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1591 foreach my $property ($ip, $gw) {
1592 if ($newnet->{$property}) {
1593 $optdata->{$property} = $newnet->{$property};
1595 delete $optdata->{$property};
1598 $conf->{$opt} = print_lxc_network
($optdata);
1599 write_config
($vmid, $conf);
1600 $lxc_setup->setup_network($conf);
1603 &$change_ip_config(4);
1604 &$change_ip_config(6);
1608 # Internal snapshots
1610 # NOTE: Snapshot create/delete involves several non-atomic
1611 # action, and can take a long time.
1612 # So we try to avoid locking the file and use 'lock' variable
1613 # inside the config file instead.
1615 my $snapshot_copy_config = sub {
1616 my ($source, $dest) = @_;
1618 foreach my $k (keys %$source) {
1619 next if $k eq 'snapshots';
1620 next if $k eq 'snapstate';
1621 next if $k eq 'snaptime';
1622 next if $k eq 'vmstate';
1623 next if $k eq 'lock';
1624 next if $k eq 'digest';
1625 next if $k eq 'description';
1627 $dest->{$k} = $source->{$k};
1631 my $snapshot_prepare = sub {
1632 my ($vmid, $snapname, $comment) = @_;
1636 my $updatefn = sub {
1638 my $conf = load_config
($vmid);
1640 die "you can't take a snapshot if it's a template\n"
1641 if is_template
($conf);
1645 $conf->{lock} = 'snapshot';
1647 die "snapshot name '$snapname' already used\n"
1648 if defined($conf->{snapshots
}->{$snapname});
1650 my $storecfg = PVE
::Storage
::config
();
1651 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1653 $snap = $conf->{snapshots
}->{$snapname} = {};
1655 &$snapshot_copy_config($conf, $snap);
1657 $snap->{'snapstate'} = "prepare";
1658 $snap->{'snaptime'} = time();
1659 $snap->{'description'} = $comment if $comment;
1660 $conf->{snapshots
}->{$snapname} = $snap;
1662 write_config
($vmid, $conf);
1665 lock_container
($vmid, 10, $updatefn);
1670 my $snapshot_commit = sub {
1671 my ($vmid, $snapname) = @_;
1673 my $updatefn = sub {
1675 my $conf = load_config
($vmid);
1677 die "missing snapshot lock\n"
1678 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1680 die "snapshot '$snapname' does not exist\n"
1681 if !defined($conf->{snapshots
}->{$snapname});
1683 die "wrong snapshot state\n"
1684 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1685 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1687 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1688 delete $conf->{lock};
1689 $conf->{parent
} = $snapname;
1691 write_config
($vmid, $conf);
1694 lock_container
($vmid, 10 ,$updatefn);
1698 my ($feature, $conf, $storecfg, $snapname) = @_;
1702 foreach_mountpoint
($conf, sub {
1703 my ($ms, $mountpoint) = @_;
1705 return if $err; # skip further test
1707 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1709 # TODO: implement support for mountpoints
1710 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1714 return $err ?
0 : 1;
1717 sub snapshot_create
{
1718 my ($vmid, $snapname, $comment) = @_;
1720 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1722 my $conf = load_config
($vmid);
1724 my $running = check_running
($vmid);
1727 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1728 PVE
::Tools
::run_command
(['/bin/sync']);
1731 my $storecfg = PVE
::Storage
::config
();
1732 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1733 my $volid = $rootinfo->{volume
};
1736 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1739 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1740 &$snapshot_commit($vmid, $snapname);
1743 snapshot_delete
($vmid, $snapname, 1);
1748 sub snapshot_delete
{
1749 my ($vmid, $snapname, $force) = @_;
1755 my $updatefn = sub {
1757 $conf = load_config
($vmid);
1759 die "you can't delete a snapshot if vm is a template\n"
1760 if is_template
($conf);
1762 $snap = $conf->{snapshots
}->{$snapname};
1766 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1768 $snap->{snapstate
} = 'delete';
1770 write_config
($vmid, $conf);
1773 lock_container
($vmid, 10, $updatefn);
1775 my $storecfg = PVE
::Storage
::config
();
1777 my $del_snap = sub {
1781 if ($conf->{parent
} eq $snapname) {
1782 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1783 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1785 delete $conf->{parent
};
1789 delete $conf->{snapshots
}->{$snapname};
1791 write_config
($vmid, $conf);
1794 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1795 my $rootinfo = parse_ct_mountpoint
($rootfs);
1796 my $volid = $rootinfo->{volume
};
1799 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1803 if(!$err || ($err && $force)) {
1804 lock_container
($vmid, 10, $del_snap);
1806 die "Can't delete snapshot: $vmid $snapname $err\n";
1811 sub snapshot_rollback
{
1812 my ($vmid, $snapname) = @_;
1814 my $storecfg = PVE
::Storage
::config
();
1816 my $conf = load_config
($vmid);
1818 die "you can't rollback if vm is a template\n" if is_template
($conf);
1820 my $snap = $conf->{snapshots
}->{$snapname};
1822 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1824 my $rootfs = $snap->{rootfs
};
1825 my $rootinfo = parse_ct_mountpoint
($rootfs);
1826 my $volid = $rootinfo->{volume
};
1828 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1830 my $updatefn = sub {
1832 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1833 if $snap->{snapstate
};
1837 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1839 die "unable to rollback vm $vmid: vm is running\n"
1840 if check_running
($vmid);
1842 $conf->{lock} = 'rollback';
1846 # copy snapshot config to current config
1848 my $tmp_conf = $conf;
1849 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1850 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1851 delete $conf->{snaptime
};
1852 delete $conf->{snapname
};
1853 $conf->{parent
} = $snapname;
1855 write_config
($vmid, $conf);
1858 my $unlockfn = sub {
1859 delete $conf->{lock};
1860 write_config
($vmid, $conf);
1863 lock_container
($vmid, 10, $updatefn);
1865 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1867 lock_container
($vmid, 5, $unlockfn);
1870 sub template_create
{
1871 my ($vmid, $conf) = @_;
1873 my $storecfg = PVE
::Storage
::config
();
1875 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1876 my $volid = $rootinfo->{volume
};
1878 die "Template feature is not available for '$volid'\n"
1879 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1881 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1883 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1884 $rootinfo->{volume
} = $template_volid;
1885 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1887 write_config
($vmid, $conf);
1893 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1896 sub mountpoint_names
{
1899 my @names = ('rootfs');
1901 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1902 push @names, "mp$i";
1905 return $reverse ?
reverse @names : @names;
1908 # The container might have *different* symlinks than the host. realpath/abs_path
1909 # use the actual filesystem to resolve links.
1910 sub sanitize_mountpoint
{
1912 $mp = '/' . $mp; # we always start with a slash
1913 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1914 $mp =~ s
@/\./@@g; # collapse /./
1915 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1916 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1917 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1921 sub foreach_mountpoint_full
{
1922 my ($conf, $reverse, $func) = @_;
1924 foreach my $key (mountpoint_names
($reverse)) {
1925 my $value = $conf->{$key};
1926 next if !defined($value);
1927 my $mountpoint = parse_ct_mountpoint
($value);
1929 # just to be sure: rootfs is /
1930 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1931 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1933 $path = $mountpoint->{volume
};
1934 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1936 &$func($key, $mountpoint);
1940 sub foreach_mountpoint
{
1941 my ($conf, $func) = @_;
1943 foreach_mountpoint_full
($conf, 0, $func);
1946 sub foreach_mountpoint_reverse
{
1947 my ($conf, $func) = @_;
1949 foreach_mountpoint_full
($conf, 1, $func);
1952 sub check_ct_modify_config_perm
{
1953 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1955 return 1 if $authuser ne 'root@pam';
1957 foreach my $opt (@$key_list) {
1959 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1960 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1961 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1962 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1963 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1964 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1965 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1966 $opt eq 'searchdomain' || $opt eq 'hostname') {
1967 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1969 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1977 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1979 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1980 my $volid_list = get_vm_volumes
($conf);
1982 foreach_mountpoint_reverse
($conf, sub {
1983 my ($ms, $mountpoint) = @_;
1985 my $volid = $mountpoint->{volume
};
1986 my $mount = $mountpoint->{mp
};
1988 return if !$volid || !$mount;
1990 my $mount_path = "$rootdir/$mount";
1991 $mount_path =~ s!/+!/!g;
1993 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1996 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2009 my ($vmid, $storage_cfg, $conf) = @_;
2011 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2012 File
::Path
::make_path
($rootdir);
2014 my $volid_list = get_vm_volumes
($conf);
2015 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2018 foreach_mountpoint
($conf, sub {
2019 my ($ms, $mountpoint) = @_;
2021 my $volid = $mountpoint->{volume
};
2022 my $mount = $mountpoint->{mp
};
2024 return if !$volid || !$mount;
2026 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2027 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2028 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2030 die "unable to mount base volume - internal error" if $isBase;
2032 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2036 warn "mounting container failed - $err";
2037 umount_all
($vmid, $storage_cfg, $conf, 1);
2044 sub mountpoint_mount_path
{
2045 my ($mountpoint, $storage_cfg, $snapname) = @_;
2047 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2050 my $check_mount_path = sub {
2052 $path = File
::Spec-
>canonpath($path);
2053 my $real = Cwd
::realpath
($path);
2054 if ($real ne $path) {
2055 die "mount path modified by symlink: $path != $real";
2059 # use $rootdir = undef to just return the corresponding mount path
2060 sub mountpoint_mount
{
2061 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2063 my $volid = $mountpoint->{volume
};
2064 my $mount = $mountpoint->{mp
};
2066 return if !$volid || !$mount;
2070 if (defined($rootdir)) {
2071 $rootdir =~ s!/+$!!;
2072 $mount_path = "$rootdir/$mount";
2073 $mount_path =~ s!/+!/!g;
2074 &$check_mount_path($mount_path);
2075 File
::Path
::mkpath
($mount_path);
2078 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2080 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2084 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2085 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2087 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2088 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2090 if ($format eq 'subvol') {
2093 if ($scfg->{type
} eq 'zfspool') {
2094 my $path_arg = $path;
2095 $path_arg =~ s!^/+!!;
2096 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2098 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2101 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2104 return wantarray ?
($path, 0) : $path;
2105 } elsif ($format eq 'raw') {
2106 my $use_loopdev = 0;
2108 if ($scfg->{path
}) {
2109 push @extra_opts, '-o', 'loop';
2111 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2114 die "unsupported storage type '$scfg->{type}'\n";
2117 if ($isBase || defined($snapname)) {
2118 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2120 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2123 return wantarray ?
($path, $use_loopdev) : $path;
2125 die "unsupported image format '$format'\n";
2127 } elsif ($volid =~ m
|^/dev/.+|) {
2128 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2129 return wantarray ?
($volid, 0) : $volid;
2130 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2131 &$check_mount_path($volid);
2132 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2133 return wantarray ?
($volid, 0) : $volid;
2136 die "unsupported storage";
2139 sub get_vm_volumes
{
2140 my ($conf, $excludes) = @_;
2144 foreach_mountpoint
($conf, sub {
2145 my ($ms, $mountpoint) = @_;
2147 return if $excludes && $ms eq $excludes;
2149 my $volid = $mountpoint->{volume
};
2151 return if !$volid || $volid =~ m
|^/|;
2153 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2156 push @$vollist, $volid;
2165 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2169 my ($storage_cfg, $volid) = @_;
2171 if ($volid =~ m!^/dev/.+!) {
2176 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2178 die "cannot format volume '$volid' with no storage\n" if !$storage;
2180 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2182 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2184 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2185 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2187 die "cannot format volume '$volid' (format == $format)\n"
2188 if $format ne 'raw';
2194 my ($storecfg, $vollist) = @_;
2196 foreach my $volid (@$vollist) {
2197 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2203 my ($storecfg, $vmid, $settings, $conf) = @_;
2208 foreach_mountpoint
($settings, sub {
2209 my ($ms, $mountpoint) = @_;
2211 my $volid = $mountpoint->{volume
};
2212 my $mp = $mountpoint->{mp
};
2214 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2216 return if !$storage;
2218 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2219 my ($storeid, $size_gb) = ($1, $2);
2221 my $size_kb = int(${size_gb
}*1024) * 1024;
2223 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2224 # fixme: use better naming ct-$vmid-disk-X.raw?
2226 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2228 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2230 format_disk
($storecfg, $volid);
2232 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2235 } elsif ($scfg->{type
} eq 'zfspool') {
2237 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2239 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2241 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2242 format_disk
($storecfg, $volid);
2244 } elsif ($scfg->{type
} eq 'rbd') {
2246 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2247 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2248 format_disk
($storecfg, $volid);
2250 die "unable to create containers on storage type '$scfg->{type}'\n";
2252 push @$vollist, $volid;
2253 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2254 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2256 # use specified/existing volid
2260 # free allocated images on error
2262 destroy_disks
($storecfg, $vollist);
2268 # bash completion helper
2270 sub complete_os_templates
{
2271 my ($cmdname, $pname, $cvalue) = @_;
2273 my $cfg = PVE
::Storage
::config
();
2277 if ($cvalue =~ m/^([^:]+):/) {
2281 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2282 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2285 foreach my $id (keys %$data) {
2286 foreach my $item (@{$data->{$id}}) {
2287 push @$res, $item->{volid
} if defined($item->{volid
});
2294 my $complete_ctid_full = sub {
2297 my $idlist = vmstatus
();
2299 my $active_hash = list_active_containers
();
2303 foreach my $id (keys %$idlist) {
2304 my $d = $idlist->{$id};
2305 if (defined($running)) {
2306 next if $d->{template
};
2307 next if $running && !$active_hash->{$id};
2308 next if !$running && $active_hash->{$id};
2317 return &$complete_ctid_full();
2320 sub complete_ctid_stopped
{
2321 return &$complete_ctid_full(0);
2324 sub complete_ctid_running
{
2325 return &$complete_ctid_full(1);