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
=> 'pve-lxc-network',
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
{
867 foreach my $p (split (/,/, $data)) {
868 next if $p =~ m/^\s*$/;
870 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
871 my ($k, $v) = ($1, $2);
872 return undef if defined($res->{$k});
875 if (!$res->{volume
} && $p !~ m/=/) {
883 return undef if !defined($res->{volume
});
885 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
888 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
894 sub print_ct_mountpoint
{
895 my ($info, $nomp) = @_;
899 die "missing volume\n" if !$info->{volume
};
901 foreach my $o (qw(backup)) {
902 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
906 $opts .= ",size=" . &$format_size($info->{size
});
909 $opts .= ",mp=$info->{mp}" if !$nomp;
911 return "$info->{volume}$opts";
914 sub print_lxc_network
{
917 die "no network name defined\n" if !$net->{name
};
919 my $res = "name=$net->{name}";
921 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
922 next if !defined($net->{$k});
923 $res .= ",$k=$net->{$k}";
929 sub parse_lxc_network
{
934 return $res if !$data;
936 foreach my $pv (split (/,/, $data)) {
937 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
944 $res->{type
} = 'veth';
945 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
950 sub read_cgroup_value
{
951 my ($group, $vmid, $name, $full) = @_;
953 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
955 return PVE
::Tools
::file_get_contents
($path) if $full;
957 return PVE
::Tools
::file_read_firstline
($path);
960 sub write_cgroup_value
{
961 my ($group, $vmid, $name, $value) = @_;
963 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
964 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
968 sub find_lxc_console_pids
{
972 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
975 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
978 my @args = split(/\0/, $cmdline);
980 # serach for lxc-console -n <vmid>
981 return if scalar(@args) != 3;
982 return if $args[1] ne '-n';
983 return if $args[2] !~ m/^\d+$/;
984 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
988 push @{$res->{$vmid}}, $pid;
1000 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1002 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
1004 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1009 my $ipv4_reverse_mask = [
1045 # Note: we cannot use Net:IP, because that only allows strict
1047 sub parse_ipv4_cidr
{
1048 my ($cidr, $noerr) = @_;
1050 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1051 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
1054 return undef if $noerr;
1056 die "unable to parse ipv4 address/mask\n";
1062 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1065 sub check_protection
{
1066 my ($vm_conf, $err_msg) = @_;
1068 if ($vm_conf->{protection
}) {
1069 die "$err_msg - protection mode enabled\n";
1073 sub update_lxc_config
{
1074 my ($storage_cfg, $vmid, $conf) = @_;
1076 my $dir = "/var/lib/lxc/$vmid";
1078 if ($conf->{template
}) {
1080 unlink "$dir/config";
1087 die "missing 'arch' - internal error" if !$conf->{arch
};
1088 $raw .= "lxc.arch = $conf->{arch}\n";
1090 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1091 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1092 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1097 if (!has_dev_console
($conf)) {
1098 $raw .= "lxc.console = none\n";
1099 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1102 my $ttycount = get_tty_count
($conf);
1103 $raw .= "lxc.tty = $ttycount\n";
1105 my $utsname = $conf->{hostname
} || "CT$vmid";
1106 $raw .= "lxc.utsname = $utsname\n";
1108 my $memory = $conf->{memory
} || 512;
1109 my $swap = $conf->{swap
} // 0;
1111 my $lxcmem = int($memory*1024*1024);
1112 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1114 my $lxcswap = int(($memory + $swap)*1024*1024);
1115 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1117 if (my $cpulimit = $conf->{cpulimit
}) {
1118 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1119 my $value = int(100000*$cpulimit);
1120 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1123 my $shares = $conf->{cpuunits
} || 1024;
1124 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1126 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1127 $mountpoint->{mp
} = '/';
1129 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1130 $path = "loop:$path" if $use_loopdev;
1132 $raw .= "lxc.rootfs = $path\n";
1135 foreach my $k (keys %$conf) {
1136 next if $k !~ m/^net(\d+)$/;
1138 my $d = parse_lxc_network
($conf->{$k});
1140 $raw .= "lxc.network.type = veth\n";
1141 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1142 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1143 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1144 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1147 if (my $lxcconf = $conf->{lxc
}) {
1148 foreach my $entry (@$lxcconf) {
1149 my ($k, $v) = @$entry;
1150 $netcount++ if $k eq 'lxc.network.type';
1151 $raw .= "$k = $v\n";
1155 $raw .= "lxc.network.type = empty\n" if !$netcount;
1157 File
::Path
::mkpath
("$dir/rootfs");
1159 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1162 # verify and cleanup nameserver list (replace \0 with ' ')
1163 sub verify_nameserver_list
{
1164 my ($nameserver_list) = @_;
1167 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1168 PVE
::JSONSchema
::pve_verify_ip
($server);
1169 push @list, $server;
1172 return join(' ', @list);
1175 sub verify_searchdomain_list
{
1176 my ($searchdomain_list) = @_;
1179 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1180 # todo: should we add checks for valid dns domains?
1181 push @list, $server;
1184 return join(' ', @list);
1187 sub update_pct_config
{
1188 my ($vmid, $conf, $running, $param, $delete) = @_;
1196 my $pid = find_lxc_pid
($vmid);
1197 $rootdir = "/proc/$pid/root";
1200 if (defined($delete)) {
1201 foreach my $opt (@$delete) {
1202 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1203 die "unable to delete required option '$opt'\n";
1204 } elsif ($opt eq 'swap') {
1205 delete $conf->{$opt};
1206 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1207 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1208 delete $conf->{$opt};
1209 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1210 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1211 delete $conf->{$opt};
1212 push @nohotplug, $opt;
1214 } elsif ($opt =~ m/^net(\d)$/) {
1215 delete $conf->{$opt};
1218 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1219 } elsif ($opt eq 'protection') {
1220 delete $conf->{$opt};
1221 } elsif ($opt =~ m/^mp(\d+)$/) {
1222 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1223 delete $conf->{$opt};
1224 push @nohotplug, $opt;
1229 write_config
($vmid, $conf) if $running;
1233 # There's no separate swap size to configure, there's memory and "total"
1234 # memory (iow. memory+swap). This means we have to change them together.
1235 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1236 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1237 if (defined($wanted_memory) || defined($wanted_swap)) {
1239 $wanted_memory //= ($conf->{memory
} || 512);
1240 $wanted_swap //= ($conf->{swap
} || 0);
1242 my $total = $wanted_memory + $wanted_swap;
1244 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1245 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1247 $conf->{memory
} = $wanted_memory;
1248 $conf->{swap
} = $wanted_swap;
1250 write_config
($vmid, $conf) if $running;
1253 foreach my $opt (keys %$param) {
1254 my $value = $param->{$opt};
1255 if ($opt eq 'hostname') {
1256 $conf->{$opt} = $value;
1257 } elsif ($opt eq 'onboot') {
1258 $conf->{$opt} = $value ?
1 : 0;
1259 } elsif ($opt eq 'startup') {
1260 $conf->{$opt} = $value;
1261 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1262 $conf->{$opt} = $value;
1263 push @nohotplug, $opt;
1265 } elsif ($opt eq 'nameserver') {
1266 my $list = verify_nameserver_list
($value);
1267 $conf->{$opt} = $list;
1268 push @nohotplug, $opt;
1270 } elsif ($opt eq 'searchdomain') {
1271 my $list = verify_searchdomain_list
($value);
1272 $conf->{$opt} = $list;
1273 push @nohotplug, $opt;
1275 } elsif ($opt eq 'cpulimit') {
1276 $conf->{$opt} = $value;
1277 push @nohotplug, $opt; # fixme: hotplug
1279 } elsif ($opt eq 'cpuunits') {
1280 $conf->{$opt} = $value;
1281 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1282 } elsif ($opt eq 'description') {
1283 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1284 } elsif ($opt =~ m/^net(\d+)$/) {
1286 my $net = parse_lxc_network
($value);
1288 $conf->{$opt} = print_lxc_network
($net);
1290 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1292 } elsif ($opt eq 'protection') {
1293 $conf->{$opt} = $value ?
1 : 0;
1294 } elsif ($opt =~ m/^mp(\d+)$/) {
1295 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1296 $conf->{$opt} = $value;
1298 push @nohotplug, $opt;
1300 } elsif ($opt eq 'rootfs') {
1301 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1302 die "implement me: $opt";
1304 die "implement me: $opt";
1306 write_config
($vmid, $conf) if $running;
1309 if ($running && scalar(@nohotplug)) {
1310 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1314 my $storage_cfg = PVE
::Storage
::config
();
1315 create_disks
($storage_cfg, $vmid, $conf, $conf);
1319 sub has_dev_console
{
1322 return !(defined($conf->{console
}) && !$conf->{console
});
1328 return $conf->{tty
} // $confdesc->{tty
}->{default};
1334 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1337 sub get_console_command
{
1338 my ($vmid, $conf) = @_;
1340 my $cmode = get_cmode
($conf);
1342 if ($cmode eq 'console') {
1343 return ['lxc-console', '-n', $vmid, '-t', 0];
1344 } elsif ($cmode eq 'tty') {
1345 return ['lxc-console', '-n', $vmid];
1346 } elsif ($cmode eq 'shell') {
1347 return ['lxc-attach', '--clear-env', '-n', $vmid];
1349 die "internal error";
1353 sub get_primary_ips
{
1356 # return data from net0
1358 return undef if !defined($conf->{net0
});
1359 my $net = parse_lxc_network
($conf->{net0
});
1361 my $ipv4 = $net->{ip
};
1363 if ($ipv4 =~ /^(dhcp|manual)$/) {
1369 my $ipv6 = $net->{ip6
};
1371 if ($ipv6 =~ /^(dhcp|manual)$/) {
1378 return ($ipv4, $ipv6);
1382 sub destroy_lxc_container
{
1383 my ($storage_cfg, $vmid, $conf) = @_;
1385 foreach_mountpoint
($conf, sub {
1386 my ($ms, $mountpoint) = @_;
1388 # skip bind mounts and block devices
1389 if ($mountpoint->{volume
} =~ m
|^/|) {
1393 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1394 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1397 rmdir "/var/lib/lxc/$vmid/rootfs";
1398 unlink "/var/lib/lxc/$vmid/config";
1399 rmdir "/var/lib/lxc/$vmid";
1400 destroy_config
($vmid);
1402 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1403 #PVE::Tools::run_command($cmd);
1406 sub vm_stop_cleanup
{
1407 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1412 my $vollist = get_vm_volumes
($conf);
1413 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1416 warn $@ if $@; # avoid errors - just warn
1419 my $safe_num_ne = sub {
1422 return 0 if !defined($a) && !defined($b);
1423 return 1 if !defined($a);
1424 return 1 if !defined($b);
1429 my $safe_string_ne = sub {
1432 return 0 if !defined($a) && !defined($b);
1433 return 1 if !defined($a);
1434 return 1 if !defined($b);
1440 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1442 if ($newnet->{type
} ne 'veth') {
1443 # for when there are physical interfaces
1444 die "cannot update interface of type $newnet->{type}";
1447 my $veth = "veth${vmid}i${netid}";
1448 my $eth = $newnet->{name
};
1450 if (my $oldnetcfg = $conf->{$opt}) {
1451 my $oldnet = parse_lxc_network
($oldnetcfg);
1453 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1454 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1456 PVE
::Network
::veth_delete
($veth);
1457 delete $conf->{$opt};
1458 write_config
($vmid, $conf);
1460 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1462 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1463 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1464 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1466 if ($oldnet->{bridge
}) {
1467 PVE
::Network
::tap_unplug
($veth);
1468 foreach (qw(bridge tag firewall)) {
1469 delete $oldnet->{$_};
1471 $conf->{$opt} = print_lxc_network
($oldnet);
1472 write_config
($vmid, $conf);
1475 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1476 foreach (qw(bridge tag firewall)) {
1477 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1479 $conf->{$opt} = print_lxc_network
($oldnet);
1480 write_config
($vmid, $conf);
1483 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1486 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1490 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1492 my $veth = "veth${vmid}i${netid}";
1493 my $vethpeer = $veth . "p";
1494 my $eth = $newnet->{name
};
1496 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1497 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1499 # attach peer in container
1500 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1501 PVE
::Tools
::run_command
($cmd);
1503 # link up peer in container
1504 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1505 PVE
::Tools
::run_command
($cmd);
1507 my $done = { type
=> 'veth' };
1508 foreach (qw(bridge tag firewall hwaddr name)) {
1509 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1511 $conf->{$opt} = print_lxc_network
($done);
1513 write_config
($vmid, $conf);
1516 sub update_ipconfig
{
1517 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1519 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1521 my $optdata = parse_lxc_network
($conf->{$opt});
1525 my $cmdargs = shift;
1526 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1528 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1530 my $change_ip_config = sub {
1531 my ($ipversion) = @_;
1533 my $family_opt = "-$ipversion";
1534 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1535 my $gw= "gw$suffix";
1536 my $ip= "ip$suffix";
1538 my $newip = $newnet->{$ip};
1539 my $newgw = $newnet->{$gw};
1540 my $oldip = $optdata->{$ip};
1542 my $change_ip = &$safe_string_ne($oldip, $newip);
1543 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1545 return if !$change_ip && !$change_gw;
1547 # step 1: add new IP, if this fails we cancel
1548 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1549 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1556 # step 2: replace gateway
1557 # If this fails we delete the added IP and cancel.
1558 # If it succeeds we save the config and delete the old IP, ignoring
1559 # errors. The config is then saved.
1560 # Note: 'ip route replace' can add
1563 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1566 # the route was not replaced, the old IP is still available
1567 # rollback (delete new IP) and cancel
1569 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1570 warn $@ if $@; # no need to die here
1575 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1576 # if the route was not deleted, the guest might have deleted it manually
1582 # from this point on we save the configuration
1583 # step 3: delete old IP ignoring errors
1584 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1585 # We need to enable promote_secondaries, otherwise our newly added
1586 # address will be removed along with the old one.
1589 if ($ipversion == 4) {
1590 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1591 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1592 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1594 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1596 warn $@ if $@; # no need to die here
1598 if ($ipversion == 4) {
1599 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1603 foreach my $property ($ip, $gw) {
1604 if ($newnet->{$property}) {
1605 $optdata->{$property} = $newnet->{$property};
1607 delete $optdata->{$property};
1610 $conf->{$opt} = print_lxc_network
($optdata);
1611 write_config
($vmid, $conf);
1612 $lxc_setup->setup_network($conf);
1615 &$change_ip_config(4);
1616 &$change_ip_config(6);
1620 # Internal snapshots
1622 # NOTE: Snapshot create/delete involves several non-atomic
1623 # action, and can take a long time.
1624 # So we try to avoid locking the file and use 'lock' variable
1625 # inside the config file instead.
1627 my $snapshot_copy_config = sub {
1628 my ($source, $dest) = @_;
1630 foreach my $k (keys %$source) {
1631 next if $k eq 'snapshots';
1632 next if $k eq 'snapstate';
1633 next if $k eq 'snaptime';
1634 next if $k eq 'vmstate';
1635 next if $k eq 'lock';
1636 next if $k eq 'digest';
1637 next if $k eq 'description';
1639 $dest->{$k} = $source->{$k};
1643 my $snapshot_prepare = sub {
1644 my ($vmid, $snapname, $comment) = @_;
1648 my $updatefn = sub {
1650 my $conf = load_config
($vmid);
1652 die "you can't take a snapshot if it's a template\n"
1653 if is_template
($conf);
1657 $conf->{lock} = 'snapshot';
1659 die "snapshot name '$snapname' already used\n"
1660 if defined($conf->{snapshots
}->{$snapname});
1662 my $storecfg = PVE
::Storage
::config
();
1663 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1665 $snap = $conf->{snapshots
}->{$snapname} = {};
1667 &$snapshot_copy_config($conf, $snap);
1669 $snap->{'snapstate'} = "prepare";
1670 $snap->{'snaptime'} = time();
1671 $snap->{'description'} = $comment if $comment;
1672 $conf->{snapshots
}->{$snapname} = $snap;
1674 write_config
($vmid, $conf);
1677 lock_container
($vmid, 10, $updatefn);
1682 my $snapshot_commit = sub {
1683 my ($vmid, $snapname) = @_;
1685 my $updatefn = sub {
1687 my $conf = load_config
($vmid);
1689 die "missing snapshot lock\n"
1690 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1692 die "snapshot '$snapname' does not exist\n"
1693 if !defined($conf->{snapshots
}->{$snapname});
1695 die "wrong snapshot state\n"
1696 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1697 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1699 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1700 delete $conf->{lock};
1701 $conf->{parent
} = $snapname;
1703 write_config
($vmid, $conf);
1706 lock_container
($vmid, 10 ,$updatefn);
1710 my ($feature, $conf, $storecfg, $snapname) = @_;
1714 foreach_mountpoint
($conf, sub {
1715 my ($ms, $mountpoint) = @_;
1717 return if $err; # skip further test
1719 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1721 # TODO: implement support for mountpoints
1722 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1726 return $err ?
0 : 1;
1729 sub snapshot_create
{
1730 my ($vmid, $snapname, $comment) = @_;
1732 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1734 my $conf = load_config
($vmid);
1736 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1737 my $running = check_running
($vmid);
1740 PVE
::Tools
::run_command
($cmd);
1743 my $storecfg = PVE
::Storage
::config
();
1744 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1745 my $volid = $rootinfo->{volume
};
1747 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1749 PVE
::Tools
::run_command
($cmd);
1752 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1753 &$snapshot_commit($vmid, $snapname);
1756 snapshot_delete
($vmid, $snapname, 1);
1761 sub snapshot_delete
{
1762 my ($vmid, $snapname, $force) = @_;
1768 my $updatefn = sub {
1770 $conf = load_config
($vmid);
1772 die "you can't delete a snapshot if vm is a template\n"
1773 if is_template
($conf);
1775 $snap = $conf->{snapshots
}->{$snapname};
1779 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1781 $snap->{snapstate
} = 'delete';
1783 write_config
($vmid, $conf);
1786 lock_container
($vmid, 10, $updatefn);
1788 my $storecfg = PVE
::Storage
::config
();
1790 my $del_snap = sub {
1794 if ($conf->{parent
} eq $snapname) {
1795 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1796 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1798 delete $conf->{parent
};
1802 delete $conf->{snapshots
}->{$snapname};
1804 write_config
($vmid, $conf);
1807 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1808 my $rootinfo = parse_ct_mountpoint
($rootfs);
1809 my $volid = $rootinfo->{volume
};
1812 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1816 if(!$err || ($err && $force)) {
1817 lock_container
($vmid, 10, $del_snap);
1819 die "Can't delete snapshot: $vmid $snapname $err\n";
1824 sub snapshot_rollback
{
1825 my ($vmid, $snapname) = @_;
1827 my $storecfg = PVE
::Storage
::config
();
1829 my $conf = load_config
($vmid);
1831 die "you can't rollback if vm is a template\n" if is_template
($conf);
1833 my $snap = $conf->{snapshots
}->{$snapname};
1835 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1837 my $rootfs = $snap->{rootfs
};
1838 my $rootinfo = parse_ct_mountpoint
($rootfs);
1839 my $volid = $rootinfo->{volume
};
1841 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1843 my $updatefn = sub {
1845 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1846 if $snap->{snapstate
};
1850 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1852 die "unable to rollback vm $vmid: vm is running\n"
1853 if check_running
($vmid);
1855 $conf->{lock} = 'rollback';
1859 # copy snapshot config to current config
1861 my $tmp_conf = $conf;
1862 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1863 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1864 delete $conf->{snaptime
};
1865 delete $conf->{snapname
};
1866 $conf->{parent
} = $snapname;
1868 write_config
($vmid, $conf);
1871 my $unlockfn = sub {
1872 delete $conf->{lock};
1873 write_config
($vmid, $conf);
1876 lock_container
($vmid, 10, $updatefn);
1878 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1880 lock_container
($vmid, 5, $unlockfn);
1883 sub template_create
{
1884 my ($vmid, $conf) = @_;
1886 my $storecfg = PVE
::Storage
::config
();
1888 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1889 my $volid = $rootinfo->{volume
};
1891 die "Template feature is not available for '$volid'\n"
1892 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1894 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1896 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1897 $rootinfo->{volume
} = $template_volid;
1898 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1900 write_config
($vmid, $conf);
1906 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1909 sub mountpoint_names
{
1912 my @names = ('rootfs');
1914 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1915 push @names, "mp$i";
1918 return $reverse ?
reverse @names : @names;
1921 # The container might have *different* symlinks than the host. realpath/abs_path
1922 # use the actual filesystem to resolve links.
1923 sub sanitize_mountpoint
{
1925 $mp = '/' . $mp; # we always start with a slash
1926 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1927 $mp =~ s
@/\./@@g; # collapse /./
1928 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1929 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1930 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1934 sub foreach_mountpoint_full
{
1935 my ($conf, $reverse, $func) = @_;
1937 foreach my $key (mountpoint_names
($reverse)) {
1938 my $value = $conf->{$key};
1939 next if !defined($value);
1940 my $mountpoint = parse_ct_mountpoint
($value);
1942 # just to be sure: rootfs is /
1943 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1944 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1946 $path = $mountpoint->{volume
};
1947 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1949 &$func($key, $mountpoint);
1953 sub foreach_mountpoint
{
1954 my ($conf, $func) = @_;
1956 foreach_mountpoint_full
($conf, 0, $func);
1959 sub foreach_mountpoint_reverse
{
1960 my ($conf, $func) = @_;
1962 foreach_mountpoint_full
($conf, 1, $func);
1965 sub check_ct_modify_config_perm
{
1966 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1968 return 1 if $authuser ne 'root@pam';
1970 foreach my $opt (@$key_list) {
1972 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1973 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1974 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1975 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1976 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1977 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1978 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1979 $opt eq 'searchdomain' || $opt eq 'hostname') {
1980 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1982 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1990 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1992 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1993 my $volid_list = get_vm_volumes
($conf);
1995 foreach_mountpoint_reverse
($conf, sub {
1996 my ($ms, $mountpoint) = @_;
1998 my $volid = $mountpoint->{volume
};
1999 my $mount = $mountpoint->{mp
};
2001 return if !$volid || !$mount;
2003 my $mount_path = "$rootdir/$mount";
2004 $mount_path =~ s!/+!/!g;
2006 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2009 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2022 my ($vmid, $storage_cfg, $conf) = @_;
2024 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2025 File
::Path
::make_path
($rootdir);
2027 my $volid_list = get_vm_volumes
($conf);
2028 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2031 foreach_mountpoint
($conf, sub {
2032 my ($ms, $mountpoint) = @_;
2034 my $volid = $mountpoint->{volume
};
2035 my $mount = $mountpoint->{mp
};
2037 return if !$volid || !$mount;
2039 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2040 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2041 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2043 die "unable to mount base volume - internal error" if $isBase;
2045 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2049 warn "mounting container failed - $err";
2050 umount_all
($vmid, $storage_cfg, $conf, 1);
2057 sub mountpoint_mount_path
{
2058 my ($mountpoint, $storage_cfg, $snapname) = @_;
2060 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2063 my $check_mount_path = sub {
2065 $path = File
::Spec-
>canonpath($path);
2066 my $real = Cwd
::realpath
($path);
2067 if ($real ne $path) {
2068 die "mount path modified by symlink: $path != $real";
2072 # use $rootdir = undef to just return the corresponding mount path
2073 sub mountpoint_mount
{
2074 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2076 my $volid = $mountpoint->{volume
};
2077 my $mount = $mountpoint->{mp
};
2079 return if !$volid || !$mount;
2083 if (defined($rootdir)) {
2084 $rootdir =~ s!/+$!!;
2085 $mount_path = "$rootdir/$mount";
2086 $mount_path =~ s!/+!/!g;
2087 &$check_mount_path($mount_path);
2088 File
::Path
::mkpath
($mount_path);
2091 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2093 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2097 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2098 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2100 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2101 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2103 if ($format eq 'subvol') {
2106 if ($scfg->{type
} eq 'zfspool') {
2107 my $path_arg = $path;
2108 $path_arg =~ s!^/+!!;
2109 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2111 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2114 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2117 return wantarray ?
($path, 0) : $path;
2118 } elsif ($format eq 'raw') {
2119 my $use_loopdev = 0;
2121 if ($scfg->{path
}) {
2122 push @extra_opts, '-o', 'loop';
2124 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2127 die "unsupported storage type '$scfg->{type}'\n";
2130 if ($isBase || defined($snapname)) {
2131 PVE
::Tools
::run_command
(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2133 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2136 return wantarray ?
($path, $use_loopdev) : $path;
2138 die "unsupported image format '$format'\n";
2140 } elsif ($volid =~ m
|^/dev/.+|) {
2141 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2142 return wantarray ?
($volid, 0) : $volid;
2143 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2144 &$check_mount_path($volid);
2145 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2146 return wantarray ?
($volid, 0) : $volid;
2149 die "unsupported storage";
2152 sub get_vm_volumes
{
2153 my ($conf, $excludes) = @_;
2157 foreach_mountpoint
($conf, sub {
2158 my ($ms, $mountpoint) = @_;
2160 return if $excludes && $ms eq $excludes;
2162 my $volid = $mountpoint->{volume
};
2164 return if !$volid || $volid =~ m
|^/|;
2166 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2169 push @$vollist, $volid;
2178 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2182 my ($storage_cfg, $volid) = @_;
2184 if ($volid =~ m!^/dev/.+!) {
2189 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2191 die "cannot format volume '$volid' with no storage\n" if !$storage;
2193 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2195 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2197 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2198 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2200 die "cannot format volume '$volid' (format == $format)\n"
2201 if $format ne 'raw';
2207 my ($storecfg, $vollist) = @_;
2209 foreach my $volid (@$vollist) {
2210 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2216 my ($storecfg, $vmid, $settings, $conf) = @_;
2221 foreach_mountpoint
($settings, sub {
2222 my ($ms, $mountpoint) = @_;
2224 my $volid = $mountpoint->{volume
};
2225 my $mp = $mountpoint->{mp
};
2227 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2229 return if !$storage;
2231 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2232 my ($storeid, $size_gb) = ($1, $2);
2234 my $size_kb = int(${size_gb
}*1024) * 1024;
2236 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2237 # fixme: use better naming ct-$vmid-disk-X.raw?
2239 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2241 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2243 format_disk
($storecfg, $volid);
2245 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2248 } elsif ($scfg->{type
} eq 'zfspool') {
2250 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2252 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2254 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2255 format_disk
($storecfg, $volid);
2257 } elsif ($scfg->{type
} eq 'rbd') {
2259 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2260 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2261 format_disk
($storecfg, $volid);
2263 die "unable to create containers on storage type '$scfg->{type}'\n";
2265 push @$vollist, $volid;
2266 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2267 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2269 # use specified/existing volid
2273 # free allocated images on error
2275 destroy_disks
($storecfg, $vollist);
2281 # bash completion helper
2283 sub complete_os_templates
{
2284 my ($cmdname, $pname, $cvalue) = @_;
2286 my $cfg = PVE
::Storage
::config
();
2290 if ($cvalue =~ m/^([^:]+):/) {
2294 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2295 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2298 foreach my $id (keys %$data) {
2299 foreach my $item (@{$data->{$id}}) {
2300 push @$res, $item->{volid
} if defined($item->{volid
});
2307 my $complete_ctid_full = sub {
2310 my $idlist = vmstatus
();
2312 my $active_hash = list_active_containers
();
2316 foreach my $id (keys %$idlist) {
2317 my $d = $idlist->{$id};
2318 if (defined($running)) {
2319 next if $d->{template
};
2320 next if $running && !$active_hash->{$id};
2321 next if !$running && $active_hash->{$id};
2330 return &$complete_ctid_full();
2333 sub complete_ctid_stopped
{
2334 return &$complete_ctid_full(0);
2337 sub complete_ctid_running
{
2338 return &$complete_ctid_full(1);