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 my $pid = find_lxc_pid
($vmid);
809 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
810 $d->{uptime
} = time - $ctime; # the method lxcfs uses
812 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
813 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
815 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
816 my @bytes = split(/\n/, $blkio_bytes);
817 foreach my $byte (@bytes) {
818 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
819 $d->{diskread
} = $2 if $key eq 'Read';
820 $d->{diskwrite
} = $2 if $key eq 'Write';
828 my $parse_size = sub {
831 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
832 my ($size, $unit) = ($1, $3);
835 $size = $size * 1024;
836 } elsif ($unit eq 'M') {
837 $size = $size * 1024 * 1024;
838 } elsif ($unit eq 'G') {
839 $size = $size * 1024 * 1024 * 1024;
845 my $format_size = sub {
850 my $kb = int($size/1024);
851 return $size if $kb*1024 != $size;
853 my $mb = int($kb/1024);
854 return "${kb}K" if $mb*1024 != $kb;
856 my $gb = int($mb/1024);
857 return "${mb}M" if $gb*1024 != $mb;
862 sub parse_ct_mountpoint
{
868 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
874 return undef if !defined($res->{volume
});
877 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
883 sub print_ct_mountpoint
{
884 my ($info, $nomp) = @_;
888 die "missing volume\n" if !$info->{volume
};
890 foreach my $o (qw(backup)) {
891 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
895 $opts .= ",size=" . &$format_size($info->{size
});
898 $opts .= ",mp=$info->{mp}" if !$nomp;
900 return "$info->{volume}$opts";
903 sub print_lxc_network
{
906 die "no network name defined\n" if !$net->{name
};
908 my $res = "name=$net->{name}";
910 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
911 next if !defined($net->{$k});
912 $res .= ",$k=$net->{$k}";
918 sub parse_lxc_network
{
923 return $res if !$data;
925 eval { $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data) };
931 $res->{type
} = 'veth';
932 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
937 sub read_cgroup_value
{
938 my ($group, $vmid, $name, $full) = @_;
940 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
942 return PVE
::Tools
::file_get_contents
($path) if $full;
944 return PVE
::Tools
::file_read_firstline
($path);
947 sub write_cgroup_value
{
948 my ($group, $vmid, $name, $value) = @_;
950 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
951 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
955 sub find_lxc_console_pids
{
959 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
962 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
965 my @args = split(/\0/, $cmdline);
967 # serach for lxc-console -n <vmid>
968 return if scalar(@args) != 3;
969 return if $args[1] ne '-n';
970 return if $args[2] !~ m/^\d+$/;
971 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
975 push @{$res->{$vmid}}, $pid;
987 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
989 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
991 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
996 my $ipv4_reverse_mask = [
1032 # Note: we cannot use Net:IP, because that only allows strict
1034 sub parse_ipv4_cidr
{
1035 my ($cidr, $noerr) = @_;
1037 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1038 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
1041 return undef if $noerr;
1043 die "unable to parse ipv4 address/mask\n";
1049 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1052 sub check_protection
{
1053 my ($vm_conf, $err_msg) = @_;
1055 if ($vm_conf->{protection
}) {
1056 die "$err_msg - protection mode enabled\n";
1060 sub update_lxc_config
{
1061 my ($storage_cfg, $vmid, $conf) = @_;
1063 my $dir = "/var/lib/lxc/$vmid";
1065 if ($conf->{template
}) {
1067 unlink "$dir/config";
1074 die "missing 'arch' - internal error" if !$conf->{arch
};
1075 $raw .= "lxc.arch = $conf->{arch}\n";
1077 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1078 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1079 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1084 if (!has_dev_console
($conf)) {
1085 $raw .= "lxc.console = none\n";
1086 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1089 my $ttycount = get_tty_count
($conf);
1090 $raw .= "lxc.tty = $ttycount\n";
1092 # some init scripts expects a linux terminal (turnkey).
1093 $raw .= "lxc.environment = TERM=linux\n";
1095 my $utsname = $conf->{hostname
} || "CT$vmid";
1096 $raw .= "lxc.utsname = $utsname\n";
1098 my $memory = $conf->{memory
} || 512;
1099 my $swap = $conf->{swap
} // 0;
1101 my $lxcmem = int($memory*1024*1024);
1102 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1104 my $lxcswap = int(($memory + $swap)*1024*1024);
1105 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1107 if (my $cpulimit = $conf->{cpulimit
}) {
1108 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1109 my $value = int(100000*$cpulimit);
1110 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1113 my $shares = $conf->{cpuunits
} || 1024;
1114 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1116 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1117 $mountpoint->{mp
} = '/';
1119 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1120 $path = "loop:$path" if $use_loopdev;
1122 $raw .= "lxc.rootfs = $path\n";
1125 foreach my $k (keys %$conf) {
1126 next if $k !~ m/^net(\d+)$/;
1128 my $d = parse_lxc_network
($conf->{$k});
1130 $raw .= "lxc.network.type = veth\n";
1131 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1132 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1133 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1134 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1137 if (my $lxcconf = $conf->{lxc
}) {
1138 foreach my $entry (@$lxcconf) {
1139 my ($k, $v) = @$entry;
1140 $netcount++ if $k eq 'lxc.network.type';
1141 $raw .= "$k = $v\n";
1145 $raw .= "lxc.network.type = empty\n" if !$netcount;
1147 File
::Path
::mkpath
("$dir/rootfs");
1149 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1152 # verify and cleanup nameserver list (replace \0 with ' ')
1153 sub verify_nameserver_list
{
1154 my ($nameserver_list) = @_;
1157 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1158 PVE
::JSONSchema
::pve_verify_ip
($server);
1159 push @list, $server;
1162 return join(' ', @list);
1165 sub verify_searchdomain_list
{
1166 my ($searchdomain_list) = @_;
1169 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1170 # todo: should we add checks for valid dns domains?
1171 push @list, $server;
1174 return join(' ', @list);
1177 sub update_pct_config
{
1178 my ($vmid, $conf, $running, $param, $delete) = @_;
1186 my $pid = find_lxc_pid
($vmid);
1187 $rootdir = "/proc/$pid/root";
1190 if (defined($delete)) {
1191 foreach my $opt (@$delete) {
1192 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1193 die "unable to delete required option '$opt'\n";
1194 } elsif ($opt eq 'swap') {
1195 delete $conf->{$opt};
1196 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1197 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1198 delete $conf->{$opt};
1199 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1200 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1201 delete $conf->{$opt};
1202 push @nohotplug, $opt;
1204 } elsif ($opt =~ m/^net(\d)$/) {
1205 delete $conf->{$opt};
1208 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1209 } elsif ($opt eq 'protection') {
1210 delete $conf->{$opt};
1211 } elsif ($opt =~ m/^mp(\d+)$/) {
1212 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1213 delete $conf->{$opt};
1214 push @nohotplug, $opt;
1219 write_config
($vmid, $conf) if $running;
1223 # There's no separate swap size to configure, there's memory and "total"
1224 # memory (iow. memory+swap). This means we have to change them together.
1225 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1226 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1227 if (defined($wanted_memory) || defined($wanted_swap)) {
1229 $wanted_memory //= ($conf->{memory
} || 512);
1230 $wanted_swap //= ($conf->{swap
} || 0);
1232 my $total = $wanted_memory + $wanted_swap;
1234 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1235 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1237 $conf->{memory
} = $wanted_memory;
1238 $conf->{swap
} = $wanted_swap;
1240 write_config
($vmid, $conf) if $running;
1243 foreach my $opt (keys %$param) {
1244 my $value = $param->{$opt};
1245 if ($opt eq 'hostname') {
1246 $conf->{$opt} = $value;
1247 } elsif ($opt eq 'onboot') {
1248 $conf->{$opt} = $value ?
1 : 0;
1249 } elsif ($opt eq 'startup') {
1250 $conf->{$opt} = $value;
1251 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1252 $conf->{$opt} = $value;
1253 push @nohotplug, $opt;
1255 } elsif ($opt eq 'nameserver') {
1256 my $list = verify_nameserver_list
($value);
1257 $conf->{$opt} = $list;
1258 push @nohotplug, $opt;
1260 } elsif ($opt eq 'searchdomain') {
1261 my $list = verify_searchdomain_list
($value);
1262 $conf->{$opt} = $list;
1263 push @nohotplug, $opt;
1265 } elsif ($opt eq 'cpulimit') {
1266 $conf->{$opt} = $value;
1267 push @nohotplug, $opt; # fixme: hotplug
1269 } elsif ($opt eq 'cpuunits') {
1270 $conf->{$opt} = $value;
1271 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1272 } elsif ($opt eq 'description') {
1273 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1274 } elsif ($opt =~ m/^net(\d+)$/) {
1276 my $net = parse_lxc_network
($value);
1278 $conf->{$opt} = print_lxc_network
($net);
1280 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1282 } elsif ($opt eq 'protection') {
1283 $conf->{$opt} = $value ?
1 : 0;
1284 } elsif ($opt =~ m/^mp(\d+)$/) {
1285 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1286 $conf->{$opt} = $value;
1288 push @nohotplug, $opt;
1290 } elsif ($opt eq 'rootfs') {
1291 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1292 die "implement me: $opt";
1294 die "implement me: $opt";
1296 write_config
($vmid, $conf) if $running;
1299 if ($running && scalar(@nohotplug)) {
1300 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1304 my $storage_cfg = PVE
::Storage
::config
();
1305 create_disks
($storage_cfg, $vmid, $conf, $conf);
1309 sub has_dev_console
{
1312 return !(defined($conf->{console
}) && !$conf->{console
});
1318 return $conf->{tty
} // $confdesc->{tty
}->{default};
1324 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1327 sub get_console_command
{
1328 my ($vmid, $conf) = @_;
1330 my $cmode = get_cmode
($conf);
1332 if ($cmode eq 'console') {
1333 return ['lxc-console', '-n', $vmid, '-t', 0];
1334 } elsif ($cmode eq 'tty') {
1335 return ['lxc-console', '-n', $vmid];
1336 } elsif ($cmode eq 'shell') {
1337 return ['lxc-attach', '--clear-env', '-n', $vmid];
1339 die "internal error";
1343 sub get_primary_ips
{
1346 # return data from net0
1348 return undef if !defined($conf->{net0
});
1349 my $net = parse_lxc_network
($conf->{net0
});
1351 my $ipv4 = $net->{ip
};
1353 if ($ipv4 =~ /^(dhcp|manual)$/) {
1359 my $ipv6 = $net->{ip6
};
1361 if ($ipv6 =~ /^(dhcp|manual)$/) {
1368 return ($ipv4, $ipv6);
1372 sub destroy_lxc_container
{
1373 my ($storage_cfg, $vmid, $conf) = @_;
1375 foreach_mountpoint
($conf, sub {
1376 my ($ms, $mountpoint) = @_;
1378 # skip bind mounts and block devices
1379 if ($mountpoint->{volume
} =~ m
|^/|) {
1383 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1384 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1387 rmdir "/var/lib/lxc/$vmid/rootfs";
1388 unlink "/var/lib/lxc/$vmid/config";
1389 rmdir "/var/lib/lxc/$vmid";
1390 destroy_config
($vmid);
1392 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1393 #PVE::Tools::run_command($cmd);
1396 sub vm_stop_cleanup
{
1397 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1402 my $vollist = get_vm_volumes
($conf);
1403 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1406 warn $@ if $@; # avoid errors - just warn
1409 my $safe_num_ne = sub {
1412 return 0 if !defined($a) && !defined($b);
1413 return 1 if !defined($a);
1414 return 1 if !defined($b);
1419 my $safe_string_ne = sub {
1422 return 0 if !defined($a) && !defined($b);
1423 return 1 if !defined($a);
1424 return 1 if !defined($b);
1430 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1432 if ($newnet->{type
} ne 'veth') {
1433 # for when there are physical interfaces
1434 die "cannot update interface of type $newnet->{type}";
1437 my $veth = "veth${vmid}i${netid}";
1438 my $eth = $newnet->{name
};
1440 if (my $oldnetcfg = $conf->{$opt}) {
1441 my $oldnet = parse_lxc_network
($oldnetcfg);
1443 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1444 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1446 PVE
::Network
::veth_delete
($veth);
1447 delete $conf->{$opt};
1448 write_config
($vmid, $conf);
1450 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1452 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1453 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1454 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1456 if ($oldnet->{bridge
}) {
1457 PVE
::Network
::tap_unplug
($veth);
1458 foreach (qw(bridge tag firewall)) {
1459 delete $oldnet->{$_};
1461 $conf->{$opt} = print_lxc_network
($oldnet);
1462 write_config
($vmid, $conf);
1465 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1466 foreach (qw(bridge tag firewall)) {
1467 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1469 $conf->{$opt} = print_lxc_network
($oldnet);
1470 write_config
($vmid, $conf);
1473 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1476 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1480 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1482 my $veth = "veth${vmid}i${netid}";
1483 my $vethpeer = $veth . "p";
1484 my $eth = $newnet->{name
};
1486 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1487 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1489 # attach peer in container
1490 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1491 PVE
::Tools
::run_command
($cmd);
1493 # link up peer in container
1494 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1495 PVE
::Tools
::run_command
($cmd);
1497 my $done = { type
=> 'veth' };
1498 foreach (qw(bridge tag firewall hwaddr name)) {
1499 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1501 $conf->{$opt} = print_lxc_network
($done);
1503 write_config
($vmid, $conf);
1506 sub update_ipconfig
{
1507 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1509 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1511 my $optdata = parse_lxc_network
($conf->{$opt});
1515 my $cmdargs = shift;
1516 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1518 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1520 my $change_ip_config = sub {
1521 my ($ipversion) = @_;
1523 my $family_opt = "-$ipversion";
1524 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1525 my $gw= "gw$suffix";
1526 my $ip= "ip$suffix";
1528 my $newip = $newnet->{$ip};
1529 my $newgw = $newnet->{$gw};
1530 my $oldip = $optdata->{$ip};
1532 my $change_ip = &$safe_string_ne($oldip, $newip);
1533 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1535 return if !$change_ip && !$change_gw;
1537 # step 1: add new IP, if this fails we cancel
1538 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1539 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1546 # step 2: replace gateway
1547 # If this fails we delete the added IP and cancel.
1548 # If it succeeds we save the config and delete the old IP, ignoring
1549 # errors. The config is then saved.
1550 # Note: 'ip route replace' can add
1553 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1556 # the route was not replaced, the old IP is still available
1557 # rollback (delete new IP) and cancel
1559 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1560 warn $@ if $@; # no need to die here
1565 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1566 # if the route was not deleted, the guest might have deleted it manually
1572 # from this point on we save the configuration
1573 # step 3: delete old IP ignoring errors
1574 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1575 # We need to enable promote_secondaries, otherwise our newly added
1576 # address will be removed along with the old one.
1579 if ($ipversion == 4) {
1580 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1581 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1582 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1584 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1586 warn $@ if $@; # no need to die here
1588 if ($ipversion == 4) {
1589 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1593 foreach my $property ($ip, $gw) {
1594 if ($newnet->{$property}) {
1595 $optdata->{$property} = $newnet->{$property};
1597 delete $optdata->{$property};
1600 $conf->{$opt} = print_lxc_network
($optdata);
1601 write_config
($vmid, $conf);
1602 $lxc_setup->setup_network($conf);
1605 &$change_ip_config(4);
1606 &$change_ip_config(6);
1610 # Internal snapshots
1612 # NOTE: Snapshot create/delete involves several non-atomic
1613 # action, and can take a long time.
1614 # So we try to avoid locking the file and use 'lock' variable
1615 # inside the config file instead.
1617 my $snapshot_copy_config = sub {
1618 my ($source, $dest) = @_;
1620 foreach my $k (keys %$source) {
1621 next if $k eq 'snapshots';
1622 next if $k eq 'snapstate';
1623 next if $k eq 'snaptime';
1624 next if $k eq 'vmstate';
1625 next if $k eq 'lock';
1626 next if $k eq 'digest';
1627 next if $k eq 'description';
1629 $dest->{$k} = $source->{$k};
1633 my $snapshot_prepare = sub {
1634 my ($vmid, $snapname, $comment) = @_;
1638 my $updatefn = sub {
1640 my $conf = load_config
($vmid);
1642 die "you can't take a snapshot if it's a template\n"
1643 if is_template
($conf);
1647 $conf->{lock} = 'snapshot';
1649 die "snapshot name '$snapname' already used\n"
1650 if defined($conf->{snapshots
}->{$snapname});
1652 my $storecfg = PVE
::Storage
::config
();
1653 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1655 $snap = $conf->{snapshots
}->{$snapname} = {};
1657 &$snapshot_copy_config($conf, $snap);
1659 $snap->{'snapstate'} = "prepare";
1660 $snap->{'snaptime'} = time();
1661 $snap->{'description'} = $comment if $comment;
1662 $conf->{snapshots
}->{$snapname} = $snap;
1664 write_config
($vmid, $conf);
1667 lock_container
($vmid, 10, $updatefn);
1672 my $snapshot_commit = sub {
1673 my ($vmid, $snapname) = @_;
1675 my $updatefn = sub {
1677 my $conf = load_config
($vmid);
1679 die "missing snapshot lock\n"
1680 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1682 die "snapshot '$snapname' does not exist\n"
1683 if !defined($conf->{snapshots
}->{$snapname});
1685 die "wrong snapshot state\n"
1686 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1687 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1689 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1690 delete $conf->{lock};
1691 $conf->{parent
} = $snapname;
1693 write_config
($vmid, $conf);
1696 lock_container
($vmid, 10 ,$updatefn);
1700 my ($feature, $conf, $storecfg, $snapname) = @_;
1704 foreach_mountpoint
($conf, sub {
1705 my ($ms, $mountpoint) = @_;
1707 return if $err; # skip further test
1709 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1711 # TODO: implement support for mountpoints
1712 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1716 return $err ?
0 : 1;
1719 sub snapshot_create
{
1720 my ($vmid, $snapname, $comment) = @_;
1722 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1724 my $conf = load_config
($vmid);
1726 my $running = check_running
($vmid);
1729 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1730 PVE
::Tools
::run_command
(['/bin/sync']);
1733 my $storecfg = PVE
::Storage
::config
();
1734 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1735 my $volid = $rootinfo->{volume
};
1738 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1741 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1742 &$snapshot_commit($vmid, $snapname);
1745 snapshot_delete
($vmid, $snapname, 1);
1750 sub snapshot_delete
{
1751 my ($vmid, $snapname, $force) = @_;
1757 my $updatefn = sub {
1759 $conf = load_config
($vmid);
1761 die "you can't delete a snapshot if vm is a template\n"
1762 if is_template
($conf);
1764 $snap = $conf->{snapshots
}->{$snapname};
1768 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1770 $snap->{snapstate
} = 'delete';
1772 write_config
($vmid, $conf);
1775 lock_container
($vmid, 10, $updatefn);
1777 my $storecfg = PVE
::Storage
::config
();
1779 my $del_snap = sub {
1783 if ($conf->{parent
} eq $snapname) {
1784 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1785 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1787 delete $conf->{parent
};
1791 delete $conf->{snapshots
}->{$snapname};
1793 write_config
($vmid, $conf);
1796 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1797 my $rootinfo = parse_ct_mountpoint
($rootfs);
1798 my $volid = $rootinfo->{volume
};
1801 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1805 if(!$err || ($err && $force)) {
1806 lock_container
($vmid, 10, $del_snap);
1808 die "Can't delete snapshot: $vmid $snapname $err\n";
1813 sub snapshot_rollback
{
1814 my ($vmid, $snapname) = @_;
1816 my $storecfg = PVE
::Storage
::config
();
1818 my $conf = load_config
($vmid);
1820 die "you can't rollback if vm is a template\n" if is_template
($conf);
1822 my $snap = $conf->{snapshots
}->{$snapname};
1824 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1826 my $rootfs = $snap->{rootfs
};
1827 my $rootinfo = parse_ct_mountpoint
($rootfs);
1828 my $volid = $rootinfo->{volume
};
1830 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1832 my $updatefn = sub {
1834 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1835 if $snap->{snapstate
};
1839 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1841 die "unable to rollback vm $vmid: vm is running\n"
1842 if check_running
($vmid);
1844 $conf->{lock} = 'rollback';
1848 # copy snapshot config to current config
1850 my $tmp_conf = $conf;
1851 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1852 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1853 delete $conf->{snaptime
};
1854 delete $conf->{snapname
};
1855 $conf->{parent
} = $snapname;
1857 write_config
($vmid, $conf);
1860 my $unlockfn = sub {
1861 delete $conf->{lock};
1862 write_config
($vmid, $conf);
1865 lock_container
($vmid, 10, $updatefn);
1867 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1869 lock_container
($vmid, 5, $unlockfn);
1872 sub template_create
{
1873 my ($vmid, $conf) = @_;
1875 my $storecfg = PVE
::Storage
::config
();
1877 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1878 my $volid = $rootinfo->{volume
};
1880 die "Template feature is not available for '$volid'\n"
1881 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1883 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1885 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1886 $rootinfo->{volume
} = $template_volid;
1887 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1889 write_config
($vmid, $conf);
1895 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1898 sub mountpoint_names
{
1901 my @names = ('rootfs');
1903 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1904 push @names, "mp$i";
1907 return $reverse ?
reverse @names : @names;
1910 # The container might have *different* symlinks than the host. realpath/abs_path
1911 # use the actual filesystem to resolve links.
1912 sub sanitize_mountpoint
{
1914 $mp = '/' . $mp; # we always start with a slash
1915 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1916 $mp =~ s
@/\./@@g; # collapse /./
1917 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1918 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1919 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1923 sub foreach_mountpoint_full
{
1924 my ($conf, $reverse, $func) = @_;
1926 foreach my $key (mountpoint_names
($reverse)) {
1927 my $value = $conf->{$key};
1928 next if !defined($value);
1929 my $mountpoint = parse_ct_mountpoint
($value);
1931 # just to be sure: rootfs is /
1932 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1933 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1935 $path = $mountpoint->{volume
};
1936 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1938 &$func($key, $mountpoint);
1942 sub foreach_mountpoint
{
1943 my ($conf, $func) = @_;
1945 foreach_mountpoint_full
($conf, 0, $func);
1948 sub foreach_mountpoint_reverse
{
1949 my ($conf, $func) = @_;
1951 foreach_mountpoint_full
($conf, 1, $func);
1954 sub check_ct_modify_config_perm
{
1955 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1957 return 1 if $authuser ne 'root@pam';
1959 foreach my $opt (@$key_list) {
1961 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1962 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1963 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1964 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1965 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1966 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1967 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1968 $opt eq 'searchdomain' || $opt eq 'hostname') {
1969 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1971 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1979 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1981 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1982 my $volid_list = get_vm_volumes
($conf);
1984 foreach_mountpoint_reverse
($conf, sub {
1985 my ($ms, $mountpoint) = @_;
1987 my $volid = $mountpoint->{volume
};
1988 my $mount = $mountpoint->{mp
};
1990 return if !$volid || !$mount;
1992 my $mount_path = "$rootdir/$mount";
1993 $mount_path =~ s!/+!/!g;
1995 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1998 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2011 my ($vmid, $storage_cfg, $conf) = @_;
2013 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2014 File
::Path
::make_path
($rootdir);
2016 my $volid_list = get_vm_volumes
($conf);
2017 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2020 foreach_mountpoint
($conf, sub {
2021 my ($ms, $mountpoint) = @_;
2023 my $volid = $mountpoint->{volume
};
2024 my $mount = $mountpoint->{mp
};
2026 return if !$volid || !$mount;
2028 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2029 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2030 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2032 die "unable to mount base volume - internal error" if $isBase;
2034 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2038 warn "mounting container failed - $err";
2039 umount_all
($vmid, $storage_cfg, $conf, 1);
2046 sub mountpoint_mount_path
{
2047 my ($mountpoint, $storage_cfg, $snapname) = @_;
2049 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2052 my $check_mount_path = sub {
2054 $path = File
::Spec-
>canonpath($path);
2055 my $real = Cwd
::realpath
($path);
2056 if ($real ne $path) {
2057 die "mount path modified by symlink: $path != $real";
2061 # use $rootdir = undef to just return the corresponding mount path
2062 sub mountpoint_mount
{
2063 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2065 my $volid = $mountpoint->{volume
};
2066 my $mount = $mountpoint->{mp
};
2068 return if !$volid || !$mount;
2072 if (defined($rootdir)) {
2073 $rootdir =~ s!/+$!!;
2074 $mount_path = "$rootdir/$mount";
2075 $mount_path =~ s!/+!/!g;
2076 &$check_mount_path($mount_path);
2077 File
::Path
::mkpath
($mount_path);
2080 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2082 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2086 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2087 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2089 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2090 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2092 if ($format eq 'subvol') {
2095 if ($scfg->{type
} eq 'zfspool') {
2096 my $path_arg = $path;
2097 $path_arg =~ s!^/+!!;
2098 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2100 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2103 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2106 return wantarray ?
($path, 0) : $path;
2107 } elsif ($format eq 'raw') {
2108 my $use_loopdev = 0;
2110 if ($scfg->{path
}) {
2111 push @extra_opts, '-o', 'loop';
2113 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2116 die "unsupported storage type '$scfg->{type}'\n";
2119 if ($isBase || defined($snapname)) {
2120 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2122 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2125 return wantarray ?
($path, $use_loopdev) : $path;
2127 die "unsupported image format '$format'\n";
2129 } elsif ($volid =~ m
|^/dev/.+|) {
2130 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2131 return wantarray ?
($volid, 0) : $volid;
2132 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2133 &$check_mount_path($volid);
2134 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2135 return wantarray ?
($volid, 0) : $volid;
2138 die "unsupported storage";
2141 sub get_vm_volumes
{
2142 my ($conf, $excludes) = @_;
2146 foreach_mountpoint
($conf, sub {
2147 my ($ms, $mountpoint) = @_;
2149 return if $excludes && $ms eq $excludes;
2151 my $volid = $mountpoint->{volume
};
2153 return if !$volid || $volid =~ m
|^/|;
2155 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2158 push @$vollist, $volid;
2167 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2171 my ($storage_cfg, $volid) = @_;
2173 if ($volid =~ m!^/dev/.+!) {
2178 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2180 die "cannot format volume '$volid' with no storage\n" if !$storage;
2182 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2184 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2186 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2187 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2189 die "cannot format volume '$volid' (format == $format)\n"
2190 if $format ne 'raw';
2196 my ($storecfg, $vollist) = @_;
2198 foreach my $volid (@$vollist) {
2199 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2205 my ($storecfg, $vmid, $settings, $conf) = @_;
2210 foreach_mountpoint
($settings, sub {
2211 my ($ms, $mountpoint) = @_;
2213 my $volid = $mountpoint->{volume
};
2214 my $mp = $mountpoint->{mp
};
2216 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2218 return if !$storage;
2220 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2221 my ($storeid, $size_gb) = ($1, $2);
2223 my $size_kb = int(${size_gb
}*1024) * 1024;
2225 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2226 # fixme: use better naming ct-$vmid-disk-X.raw?
2228 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2230 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2232 format_disk
($storecfg, $volid);
2234 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2237 } elsif ($scfg->{type
} eq 'zfspool') {
2239 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2241 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2243 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2244 format_disk
($storecfg, $volid);
2246 } elsif ($scfg->{type
} eq 'rbd') {
2248 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2249 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2250 format_disk
($storecfg, $volid);
2252 die "unable to create containers on storage type '$scfg->{type}'\n";
2254 push @$vollist, $volid;
2255 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2256 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2258 # use specified/existing volid
2262 # free allocated images on error
2264 destroy_disks
($storecfg, $vollist);
2270 # bash completion helper
2272 sub complete_os_templates
{
2273 my ($cmdname, $pname, $cvalue) = @_;
2275 my $cfg = PVE
::Storage
::config
();
2279 if ($cvalue =~ m/^([^:]+):/) {
2283 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2284 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2287 foreach my $id (keys %$data) {
2288 foreach my $item (@{$data->{$id}}) {
2289 push @$res, $item->{volid
} if defined($item->{volid
});
2296 my $complete_ctid_full = sub {
2299 my $idlist = vmstatus
();
2301 my $active_hash = list_active_containers
();
2305 foreach my $id (keys %$idlist) {
2306 my $d = $idlist->{$id};
2307 if (defined($running)) {
2308 next if $d->{template
};
2309 next if $running && !$active_hash->{$id};
2310 next if !$running && $active_hash->{$id};
2319 return &$complete_ctid_full();
2322 sub complete_ctid_stopped
{
2323 return &$complete_ctid_full(0);
2326 sub complete_ctid_running
{
2327 return &$complete_ctid_full(1);