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
);
28 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
29 sub verify_lxc_network
{
30 my ($value, $noerr) = @_;
32 return $value if parse_lxc_network
($value);
34 return undef if $noerr;
36 die "unable to parse network setting\n";
39 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
40 sub verify_ct_mountpoint
{
41 my ($value, $noerr) = @_;
43 return $value if parse_ct_mountpoint
($value);
45 return undef if $noerr;
47 die "unable to parse CT mountpoint options\n";
50 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
51 type
=> 'string', format
=> 'pve-ct-mountpoint',
52 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
53 description
=> "Use volume as container root.",
57 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
58 description
=> "The name of the snapshot.",
59 type
=> 'string', format
=> 'pve-configid',
67 description
=> "Lock/unlock the VM.",
68 enum
=> [qw(migrate backup snapshot rollback)],
73 description
=> "Specifies whether a VM will be started during system bootup.",
76 startup
=> get_standard_option
('pve-startup-order'),
80 description
=> "Enable/disable Template.",
86 enum
=> ['amd64', 'i386'],
87 description
=> "OS architecture type.",
93 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
94 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
99 description
=> "Attach a console device (/dev/console) to the container.",
105 description
=> "Specify the number of tty available to the container",
113 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.",
121 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.",
129 description
=> "Amount of RAM for the VM in MB.",
136 description
=> "Amount of SWAP for the VM in MB.",
142 description
=> "Set a host name for the container.",
149 description
=> "Container description. Only used on the configuration web interface.",
154 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
159 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.",
161 rootfs
=> get_standard_option
('pve-ct-rootfs'),
164 type
=> 'string', format
=> 'pve-configid',
166 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
170 description
=> "Timestamp for snapshots.",
176 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).",
178 enum
=> ['shell', 'console', 'tty'],
184 description
=> "Sets the protection flag of the container. This will prevent the remove operation.",
189 my $valid_lxc_conf_keys = {
193 'lxc.haltsignal' => 1,
194 'lxc.rebootsignal' => 1,
195 'lxc.stopsignal' => 1,
197 'lxc.network.type' => 1,
198 'lxc.network.flags' => 1,
199 'lxc.network.link' => 1,
200 'lxc.network.mtu' => 1,
201 'lxc.network.name' => 1,
202 'lxc.network.hwaddr' => 1,
203 'lxc.network.ipv4' => 1,
204 'lxc.network.ipv4.gateway' => 1,
205 'lxc.network.ipv6' => 1,
206 'lxc.network.ipv6.gateway' => 1,
207 'lxc.network.script.up' => 1,
208 'lxc.network.script.down' => 1,
210 'lxc.console.logfile' => 1,
213 'lxc.devttydir' => 1,
214 'lxc.hook.autodev' => 1,
218 'lxc.mount.entry' => 1,
219 'lxc.mount.auto' => 1,
221 'lxc.rootfs.mount' => 1,
222 'lxc.rootfs.options' => 1,
226 'lxc.aa_profile' => 1,
227 'lxc.aa_allow_incomplete' => 1,
228 'lxc.se_context' => 1,
231 'lxc.hook.pre-start' => 1,
232 'lxc.hook.pre-mount' => 1,
233 'lxc.hook.mount' => 1,
234 'lxc.hook.start' => 1,
235 'lxc.hook.post-stop' => 1,
236 'lxc.hook.clone' => 1,
237 'lxc.hook.destroy' => 1,
240 'lxc.start.auto' => 1,
241 'lxc.start.delay' => 1,
242 'lxc.start.order' => 1,
244 'lxc.environment' => 1,
251 my $MAX_LXC_NETWORKS = 10;
252 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
253 $confdesc->{"net$i"} = {
255 type
=> 'string', format
=> 'pve-lxc-network',
256 description
=> "Specifies network interfaces for the container.\n\n".
257 "The string should have the follow format:\n\n".
258 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
259 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
260 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
261 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
265 my $MAX_MOUNT_POINTS = 10;
266 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
267 $confdesc->{"mp$i"} = {
269 type
=> 'string', format
=> 'pve-ct-mountpoint',
270 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
271 description
=> "Use volume as container mount point (experimental feature).",
276 sub write_pct_config
{
277 my ($filename, $conf) = @_;
279 delete $conf->{snapstate
}; # just to be sure
281 my $generate_raw_config = sub {
286 # add description as comment to top of file
287 my $descr = $conf->{description
} || '';
288 foreach my $cl (split(/\n/, $descr)) {
289 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
292 foreach my $key (sort keys %$conf) {
293 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
294 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
295 $raw .= "$key: $conf->{$key}\n";
298 if (my $lxcconf = $conf->{lxc
}) {
299 foreach my $entry (@$lxcconf) {
300 my ($k, $v) = @$entry;
308 my $raw = &$generate_raw_config($conf);
310 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
311 $raw .= "\n[$snapname]\n";
312 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
319 my ($key, $value) = @_;
321 die "unknown setting '$key'\n" if !$confdesc->{$key};
323 my $type = $confdesc->{$key}->{type
};
325 if (!defined($value)) {
326 die "got undefined value\n";
329 if ($value =~ m/[\n\r]/) {
330 die "property contains a line feed\n";
333 if ($type eq 'boolean') {
334 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
335 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
336 die "type check ('boolean') failed - got '$value'\n";
337 } elsif ($type eq 'integer') {
338 return int($1) if $value =~ m/^(\d+)$/;
339 die "type check ('integer') failed - got '$value'\n";
340 } elsif ($type eq 'number') {
341 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
342 die "type check ('number') failed - got '$value'\n";
343 } elsif ($type eq 'string') {
344 if (my $fmt = $confdesc->{$key}->{format
}) {
345 PVE
::JSONSchema
::check_format
($fmt, $value);
354 sub parse_pct_config
{
355 my ($filename, $raw) = @_;
357 return undef if !defined($raw);
360 digest
=> Digest
::SHA
::sha1_hex
($raw),
364 $filename =~ m
|/lxc/(\d
+).conf
$|
365 || die "got strange filename '$filename'";
373 my @lines = split(/\n/, $raw);
374 foreach my $line (@lines) {
375 next if $line =~ m/^\s*$/;
377 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
379 $conf->{description
} = $descr if $descr;
381 $conf = $res->{snapshots
}->{$section} = {};
385 if ($line =~ m/^\#(.*)\s*$/) {
386 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
390 if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
393 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
394 push @{$conf->{lxc
}}, [$key, $value];
396 warn "vm $vmid - unable to parse config: $line\n";
398 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
399 $descr .= PVE
::Tools
::decode_text
($2);
400 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
401 $conf->{snapstate
} = $1;
402 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
405 eval { $value = check_type
($key, $value); };
406 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
407 $conf->{$key} = $value;
409 warn "vm $vmid - unable to parse config: $line\n";
413 $conf->{description
} = $descr if $descr;
415 delete $res->{snapstate
}; # just to be sure
421 my $vmlist = PVE
::Cluster
::get_vmlist
();
423 return $res if !$vmlist || !$vmlist->{ids
};
424 my $ids = $vmlist->{ids
};
426 foreach my $vmid (keys %$ids) {
427 next if !$vmid; # skip CT0
428 my $d = $ids->{$vmid};
429 next if !$d->{node
} || $d->{node
} ne $nodename;
430 next if !$d->{type
} || $d->{type
} ne 'lxc';
431 $res->{$vmid}->{type
} = 'lxc';
436 sub cfs_config_path
{
437 my ($vmid, $node) = @_;
439 $node = $nodename if !$node;
440 return "nodes/$node/lxc/$vmid.conf";
444 my ($vmid, $node) = @_;
446 my $cfspath = cfs_config_path
($vmid, $node);
447 return "/etc/pve/$cfspath";
451 my ($vmid, $node) = @_;
453 $node = $nodename if !$node;
454 my $cfspath = cfs_config_path
($vmid, $node);
456 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
457 die "container $vmid does not exists\n" if !defined($conf);
463 my ($vmid, $conf) = @_;
465 my $dir = "/etc/pve/nodes/$nodename/lxc";
468 write_config
($vmid, $conf);
474 unlink config_file
($vmid, $nodename);
478 my ($vmid, $conf) = @_;
480 my $cfspath = cfs_config_path
($vmid);
482 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
485 # flock: we use one file handle per process, so lock file
486 # can be called multiple times and succeeds for the same process.
488 my $lock_handles = {};
489 my $lockdir = "/run/lock/lxc";
494 return "$lockdir/pve-config-${vmid}.lock";
498 my ($vmid, $timeout) = @_;
500 $timeout = 10 if !$timeout;
503 my $filename = lock_filename
($vmid);
505 mkdir $lockdir if !-d
$lockdir;
507 my $lock_func = sub {
508 if (!$lock_handles->{$$}->{$filename}) {
509 my $fh = new IO
::File
(">>$filename") ||
510 die "can't open file - $!\n";
511 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
514 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
515 print STDERR
"trying to aquire lock...";
518 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
519 # try again on EINTR (see bug #273)
520 if ($success || ($! != EINTR
)) {
525 print STDERR
" failed\n";
526 die "can't aquire lock - $!\n";
529 print STDERR
" OK\n";
532 $lock_handles->{$$}->{$filename}->{refcount
}++;
535 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
538 die "can't lock file '$filename' - $err";
545 my $filename = lock_filename
($vmid);
547 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
548 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
549 if ($refcount <= 0) {
550 $lock_handles->{$$}->{$filename} = undef;
557 my ($vmid, $timeout, $code, @param) = @_;
561 lock_aquire
($vmid, $timeout);
562 eval { $res = &$code(@param) };
574 return defined($confdesc->{$name});
577 # add JSON properties for create and set function
578 sub json_config_properties
{
581 foreach my $opt (keys %$confdesc) {
582 next if $opt eq 'parent' || $opt eq 'snaptime';
583 next if $prop->{$opt};
584 $prop->{$opt} = $confdesc->{$opt};
590 sub json_config_properties_no_rootfs
{
593 foreach my $opt (keys %$confdesc) {
594 next if $prop->{$opt};
595 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
596 $prop->{$opt} = $confdesc->{$opt};
602 # container status helpers
604 sub list_active_containers
{
606 my $filename = "/proc/net/unix";
608 # similar test is used by lcxcontainers.c: list_active_containers
611 my $fh = IO
::File-
>new ($filename, "r");
614 while (defined(my $line = <$fh>)) {
615 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
617 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
628 # warning: this is slow
632 my $active_hash = list_active_containers
();
634 return 1 if defined($active_hash->{$vmid});
639 sub get_container_disk_usage
{
642 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
652 if (my ($fsid, $total, $used, $avail) = $line =~
653 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
661 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
670 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
672 my $active_hash = list_active_containers
();
674 foreach my $vmid (keys %$list) {
675 my $d = $list->{$vmid};
677 my $running = defined($active_hash->{$vmid});
679 $d->{status
} = $running ?
'running' : 'stopped';
681 my $cfspath = cfs_config_path
($vmid);
682 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
684 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
685 $d->{name
} =~ s/[\s]//g;
687 $d->{cpus
} = $conf->{cpulimit
} // 0;
690 my $res = get_container_disk_usage
($vmid);
691 $d->{disk
} = $res->{used
};
692 $d->{maxdisk
} = $res->{total
};
695 # use 4GB by default ??
696 if (my $rootfs = $conf->{rootfs
}) {
697 my $rootinfo = parse_ct_mountpoint
($rootfs);
698 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
700 $d->{maxdisk
} = 4*1024*1024*1024;
706 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
707 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
718 $d->{template
} = is_template
($conf);
721 foreach my $vmid (keys %$list) {
722 my $d = $list->{$vmid};
723 next if $d->{status
} ne 'running';
725 $d->{uptime
} = 100; # fixme:
727 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
728 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
730 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
731 my @bytes = split(/\n/, $blkio_bytes);
732 foreach my $byte (@bytes) {
733 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
734 $d->{diskread
} = $2 if $key eq 'Read';
735 $d->{diskwrite
} = $2 if $key eq 'Write';
743 my $parse_size = sub {
746 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
747 my ($size, $unit) = ($1, $3);
750 $size = $size * 1024;
751 } elsif ($unit eq 'M') {
752 $size = $size * 1024 * 1024;
753 } elsif ($unit eq 'G') {
754 $size = $size * 1024 * 1024 * 1024;
760 my $format_size = sub {
765 my $kb = int($size/1024);
766 return $size if $kb*1024 != $size;
768 my $mb = int($kb/1024);
769 return "${kb}K" if $mb*1024 != $kb;
771 my $gb = int($mb/1024);
772 return "${mb}M" if $gb*1024 != $mb;
777 sub parse_ct_mountpoint
{
784 foreach my $p (split (/,/, $data)) {
785 next if $p =~ m/^\s*$/;
787 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
788 my ($k, $v) = ($1, $2);
789 return undef if defined($res->{$k});
792 if (!$res->{volume
} && $p !~ m/=/) {
800 return undef if !defined($res->{volume
});
802 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
805 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
811 sub print_ct_mountpoint
{
812 my ($info, $nomp) = @_;
816 die "missing volume\n" if !$info->{volume
};
818 foreach my $o (qw(backup)) {
819 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
823 $opts .= ",size=" . &$format_size($info->{size
});
826 $opts .= ",mp=$info->{mp}" if !$nomp;
828 return "$info->{volume}$opts";
831 sub print_lxc_network
{
834 die "no network name defined\n" if !$net->{name
};
836 my $res = "name=$net->{name}";
838 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
839 next if !defined($net->{$k});
840 $res .= ",$k=$net->{$k}";
846 sub parse_lxc_network
{
851 return $res if !$data;
853 foreach my $pv (split (/,/, $data)) {
854 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
861 $res->{type
} = 'veth';
862 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
867 sub read_cgroup_value
{
868 my ($group, $vmid, $name, $full) = @_;
870 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
872 return PVE
::Tools
::file_get_contents
($path) if $full;
874 return PVE
::Tools
::file_read_firstline
($path);
877 sub write_cgroup_value
{
878 my ($group, $vmid, $name, $value) = @_;
880 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
881 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
885 sub find_lxc_console_pids
{
889 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
892 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
895 my @args = split(/\0/, $cmdline);
897 # serach for lxc-console -n <vmid>
898 return if scalar(@args) != 3;
899 return if $args[1] ne '-n';
900 return if $args[2] !~ m/^\d+$/;
901 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
905 push @{$res->{$vmid}}, $pid;
917 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
919 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
921 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
926 my $ipv4_reverse_mask = [
962 # Note: we cannot use Net:IP, because that only allows strict
964 sub parse_ipv4_cidr
{
965 my ($cidr, $noerr) = @_;
967 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
968 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
971 return undef if $noerr;
973 die "unable to parse ipv4 address/mask\n";
979 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
982 sub update_lxc_config
{
983 my ($storage_cfg, $vmid, $conf) = @_;
985 my $dir = "/var/lib/lxc/$vmid";
987 if ($conf->{template
}) {
989 unlink "$dir/config";
996 die "missing 'arch' - internal error" if !$conf->{arch
};
997 $raw .= "lxc.arch = $conf->{arch}\n";
999 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1000 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1001 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1006 if (!has_dev_console
($conf)) {
1007 $raw .= "lxc.console = none\n";
1008 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1011 my $ttycount = get_tty_count
($conf);
1012 $raw .= "lxc.tty = $ttycount\n";
1014 my $utsname = $conf->{hostname
} || "CT$vmid";
1015 $raw .= "lxc.utsname = $utsname\n";
1017 my $memory = $conf->{memory
} || 512;
1018 my $swap = $conf->{swap
} // 0;
1020 my $lxcmem = int($memory*1024*1024);
1021 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1023 my $lxcswap = int(($memory + $swap)*1024*1024);
1024 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1026 if (my $cpulimit = $conf->{cpulimit
}) {
1027 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1028 my $value = int(100000*$cpulimit);
1029 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1032 my $shares = $conf->{cpuunits
} || 1024;
1033 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1035 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1036 $mountpoint->{mp
} = '/';
1038 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1039 $path = "loop:$path" if $use_loopdev;
1041 $raw .= "lxc.rootfs = $path\n";
1044 foreach my $k (keys %$conf) {
1045 next if $k !~ m/^net(\d+)$/;
1047 my $d = parse_lxc_network
($conf->{$k});
1049 $raw .= "lxc.network.type = veth\n";
1050 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1051 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1052 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1053 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1056 if (my $lxcconf = $conf->{lxc
}) {
1057 foreach my $entry (@$lxcconf) {
1058 my ($k, $v) = @$entry;
1059 $netcount++ if $k eq 'lxc.network.type';
1060 $raw .= "$k = $v\n";
1064 $raw .= "lxc.network.type = empty\n" if !$netcount;
1066 File
::Path
::mkpath
("$dir/rootfs");
1068 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1071 # verify and cleanup nameserver list (replace \0 with ' ')
1072 sub verify_nameserver_list
{
1073 my ($nameserver_list) = @_;
1076 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1077 PVE
::JSONSchema
::pve_verify_ip
($server);
1078 push @list, $server;
1081 return join(' ', @list);
1084 sub verify_searchdomain_list
{
1085 my ($searchdomain_list) = @_;
1088 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1089 # todo: should we add checks for valid dns domains?
1090 push @list, $server;
1093 return join(' ', @list);
1096 sub update_pct_config
{
1097 my ($vmid, $conf, $running, $param, $delete) = @_;
1105 my $pid = find_lxc_pid
($vmid);
1106 $rootdir = "/proc/$pid/root";
1109 if (defined($delete)) {
1110 foreach my $opt (@$delete) {
1111 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1112 die "unable to delete required option '$opt'\n";
1113 } elsif ($opt eq 'swap') {
1114 delete $conf->{$opt};
1115 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1116 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1117 delete $conf->{$opt};
1118 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1119 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1120 delete $conf->{$opt};
1121 push @nohotplug, $opt;
1123 } elsif ($opt =~ m/^net(\d)$/) {
1124 delete $conf->{$opt};
1127 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1128 } elsif ($opt eq 'protection') {
1129 delete $conf->{$opt};
1130 } elsif ($opt =~ m/^mp(\d+)$/) {
1131 delete $conf->{$opt};
1132 push @nohotplug, $opt;
1134 } elsif ($opt eq 'rootfs') {
1139 write_config
($vmid, $conf) if $running;
1143 # There's no separate swap size to configure, there's memory and "total"
1144 # memory (iow. memory+swap). This means we have to change them together.
1145 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1146 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1147 if (defined($wanted_memory) || defined($wanted_swap)) {
1149 $wanted_memory //= ($conf->{memory
} || 512);
1150 $wanted_swap //= ($conf->{swap
} || 0);
1152 my $total = $wanted_memory + $wanted_swap;
1154 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1155 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1157 $conf->{memory
} = $wanted_memory;
1158 $conf->{swap
} = $wanted_swap;
1160 write_config
($vmid, $conf) if $running;
1163 foreach my $opt (keys %$param) {
1164 my $value = $param->{$opt};
1165 if ($opt eq 'hostname') {
1166 $conf->{$opt} = $value;
1167 } elsif ($opt eq 'onboot') {
1168 $conf->{$opt} = $value ?
1 : 0;
1169 } elsif ($opt eq 'startup') {
1170 $conf->{$opt} = $value;
1171 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1172 $conf->{$opt} = $value;
1173 push @nohotplug, $opt;
1175 } elsif ($opt eq 'nameserver') {
1176 my $list = verify_nameserver_list
($value);
1177 $conf->{$opt} = $list;
1178 push @nohotplug, $opt;
1180 } elsif ($opt eq 'searchdomain') {
1181 my $list = verify_searchdomain_list
($value);
1182 $conf->{$opt} = $list;
1183 push @nohotplug, $opt;
1185 } elsif ($opt eq 'cpulimit') {
1186 $conf->{$opt} = $value;
1187 push @nohotplug, $opt; # fixme: hotplug
1189 } elsif ($opt eq 'cpuunits') {
1190 $conf->{$opt} = $value;
1191 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1192 } elsif ($opt eq 'description') {
1193 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1194 } elsif ($opt =~ m/^net(\d+)$/) {
1196 my $net = parse_lxc_network
($value);
1198 $conf->{$opt} = print_lxc_network
($net);
1200 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1202 } elsif ($opt eq 'protection') {
1203 $conf->{$opt} = $value ?
1 : 0;
1204 } elsif ($opt =~ m/^mp(\d+)$/) {
1205 $conf->{$opt} = $value;
1207 push @nohotplug, $opt;
1209 } elsif ($opt eq 'rootfs') {
1210 die "implement me: $opt";
1212 die "implement me: $opt";
1214 write_config
($vmid, $conf) if $running;
1217 if ($running && scalar(@nohotplug)) {
1218 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1222 my $storage_cfg = PVE
::Storage
::config
();
1223 create_disks
($storage_cfg, $vmid, $conf, $conf);
1227 sub has_dev_console
{
1230 return !(defined($conf->{console
}) && !$conf->{console
});
1236 return $conf->{tty
} // $confdesc->{tty
}->{default};
1242 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1245 sub get_console_command
{
1246 my ($vmid, $conf) = @_;
1248 my $cmode = get_cmode
($conf);
1250 if ($cmode eq 'console') {
1251 return ['lxc-console', '-n', $vmid, '-t', 0];
1252 } elsif ($cmode eq 'tty') {
1253 return ['lxc-console', '-n', $vmid];
1254 } elsif ($cmode eq 'shell') {
1255 return ['lxc-attach', '--clear-env', '-n', $vmid];
1257 die "internal error";
1261 sub get_primary_ips
{
1264 # return data from net0
1266 return undef if !defined($conf->{net0
});
1267 my $net = parse_lxc_network
($conf->{net0
});
1269 my $ipv4 = $net->{ip
};
1271 if ($ipv4 =~ /^(dhcp|manual)$/) {
1277 my $ipv6 = $net->{ip6
};
1279 if ($ipv6 =~ /^(dhcp|manual)$/) {
1286 return ($ipv4, $ipv6);
1290 sub destroy_lxc_container
{
1291 my ($storage_cfg, $vmid, $conf) = @_;
1293 foreach_mountpoint
($conf, sub {
1294 my ($ms, $mountpoint) = @_;
1295 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1296 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1299 rmdir "/var/lib/lxc/$vmid/rootfs";
1300 unlink "/var/lib/lxc/$vmid/config";
1301 rmdir "/var/lib/lxc/$vmid";
1302 destroy_config
($vmid);
1304 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1305 #PVE::Tools::run_command($cmd);
1308 sub vm_stop_cleanup
{
1309 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1314 my $vollist = get_vm_volumes
($conf);
1315 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1318 warn $@ if $@; # avoid errors - just warn
1321 my $safe_num_ne = sub {
1324 return 0 if !defined($a) && !defined($b);
1325 return 1 if !defined($a);
1326 return 1 if !defined($b);
1331 my $safe_string_ne = sub {
1334 return 0 if !defined($a) && !defined($b);
1335 return 1 if !defined($a);
1336 return 1 if !defined($b);
1342 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1344 if ($newnet->{type
} ne 'veth') {
1345 # for when there are physical interfaces
1346 die "cannot update interface of type $newnet->{type}";
1349 my $veth = "veth${vmid}i${netid}";
1350 my $eth = $newnet->{name
};
1352 if (my $oldnetcfg = $conf->{$opt}) {
1353 my $oldnet = parse_lxc_network
($oldnetcfg);
1355 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1356 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1358 PVE
::Network
::veth_delete
($veth);
1359 delete $conf->{$opt};
1360 write_config
($vmid, $conf);
1362 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1364 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1365 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1366 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1368 if ($oldnet->{bridge
}) {
1369 PVE
::Network
::tap_unplug
($veth);
1370 foreach (qw(bridge tag firewall)) {
1371 delete $oldnet->{$_};
1373 $conf->{$opt} = print_lxc_network
($oldnet);
1374 write_config
($vmid, $conf);
1377 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1378 foreach (qw(bridge tag firewall)) {
1379 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1381 $conf->{$opt} = print_lxc_network
($oldnet);
1382 write_config
($vmid, $conf);
1385 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1388 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1392 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1394 my $veth = "veth${vmid}i${netid}";
1395 my $vethpeer = $veth . "p";
1396 my $eth = $newnet->{name
};
1398 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1399 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1401 # attach peer in container
1402 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1403 PVE
::Tools
::run_command
($cmd);
1405 # link up peer in container
1406 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1407 PVE
::Tools
::run_command
($cmd);
1409 my $done = { type
=> 'veth' };
1410 foreach (qw(bridge tag firewall hwaddr name)) {
1411 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1413 $conf->{$opt} = print_lxc_network
($done);
1415 write_config
($vmid, $conf);
1418 sub update_ipconfig
{
1419 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1421 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1423 my $optdata = parse_lxc_network
($conf->{$opt});
1427 my $cmdargs = shift;
1428 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1430 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1432 my $change_ip_config = sub {
1433 my ($ipversion) = @_;
1435 my $family_opt = "-$ipversion";
1436 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1437 my $gw= "gw$suffix";
1438 my $ip= "ip$suffix";
1440 my $newip = $newnet->{$ip};
1441 my $newgw = $newnet->{$gw};
1442 my $oldip = $optdata->{$ip};
1444 my $change_ip = &$safe_string_ne($oldip, $newip);
1445 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1447 return if !$change_ip && !$change_gw;
1449 # step 1: add new IP, if this fails we cancel
1450 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1451 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1458 # step 2: replace gateway
1459 # If this fails we delete the added IP and cancel.
1460 # If it succeeds we save the config and delete the old IP, ignoring
1461 # errors. The config is then saved.
1462 # Note: 'ip route replace' can add
1465 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1468 # the route was not replaced, the old IP is still available
1469 # rollback (delete new IP) and cancel
1471 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1472 warn $@ if $@; # no need to die here
1477 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1478 # if the route was not deleted, the guest might have deleted it manually
1484 # from this point on we save the configuration
1485 # step 3: delete old IP ignoring errors
1486 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1487 # We need to enable promote_secondaries, otherwise our newly added
1488 # address will be removed along with the old one.
1491 if ($ipversion == 4) {
1492 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1493 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1494 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1496 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1498 warn $@ if $@; # no need to die here
1500 if ($ipversion == 4) {
1501 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1505 foreach my $property ($ip, $gw) {
1506 if ($newnet->{$property}) {
1507 $optdata->{$property} = $newnet->{$property};
1509 delete $optdata->{$property};
1512 $conf->{$opt} = print_lxc_network
($optdata);
1513 write_config
($vmid, $conf);
1514 $lxc_setup->setup_network($conf);
1517 &$change_ip_config(4);
1518 &$change_ip_config(6);
1522 # Internal snapshots
1524 # NOTE: Snapshot create/delete involves several non-atomic
1525 # action, and can take a long time.
1526 # So we try to avoid locking the file and use 'lock' variable
1527 # inside the config file instead.
1529 my $snapshot_copy_config = sub {
1530 my ($source, $dest) = @_;
1532 foreach my $k (keys %$source) {
1533 next if $k eq 'snapshots';
1534 next if $k eq 'snapstate';
1535 next if $k eq 'snaptime';
1536 next if $k eq 'vmstate';
1537 next if $k eq 'lock';
1538 next if $k eq 'digest';
1539 next if $k eq 'description';
1541 $dest->{$k} = $source->{$k};
1545 my $snapshot_prepare = sub {
1546 my ($vmid, $snapname, $comment) = @_;
1550 my $updatefn = sub {
1552 my $conf = load_config
($vmid);
1554 die "you can't take a snapshot if it's a template\n"
1555 if is_template
($conf);
1559 $conf->{lock} = 'snapshot';
1561 die "snapshot name '$snapname' already used\n"
1562 if defined($conf->{snapshots
}->{$snapname});
1564 my $storecfg = PVE
::Storage
::config
();
1565 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1567 $snap = $conf->{snapshots
}->{$snapname} = {};
1569 &$snapshot_copy_config($conf, $snap);
1571 $snap->{'snapstate'} = "prepare";
1572 $snap->{'snaptime'} = time();
1573 $snap->{'description'} = $comment if $comment;
1574 $conf->{snapshots
}->{$snapname} = $snap;
1576 write_config
($vmid, $conf);
1579 lock_container
($vmid, 10, $updatefn);
1584 my $snapshot_commit = sub {
1585 my ($vmid, $snapname) = @_;
1587 my $updatefn = sub {
1589 my $conf = load_config
($vmid);
1591 die "missing snapshot lock\n"
1592 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1594 die "snapshot '$snapname' does not exist\n"
1595 if !defined($conf->{snapshots
}->{$snapname});
1597 die "wrong snapshot state\n"
1598 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1599 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1601 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1602 delete $conf->{lock};
1603 $conf->{parent
} = $snapname;
1605 write_config
($vmid, $conf);
1608 lock_container
($vmid, 10 ,$updatefn);
1612 my ($feature, $conf, $storecfg, $snapname) = @_;
1616 foreach_mountpoint
($conf, sub {
1617 my ($ms, $mountpoint) = @_;
1619 return if $err; # skip further test
1621 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1623 # TODO: implement support for mountpoints
1624 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1628 return $err ?
0 : 1;
1631 sub snapshot_create
{
1632 my ($vmid, $snapname, $comment) = @_;
1634 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1636 my $conf = load_config
($vmid);
1638 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1639 my $running = check_running
($vmid);
1642 PVE
::Tools
::run_command
($cmd);
1645 my $storecfg = PVE
::Storage
::config
();
1646 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1647 my $volid = $rootinfo->{volume
};
1649 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1651 PVE
::Tools
::run_command
($cmd);
1654 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1655 &$snapshot_commit($vmid, $snapname);
1658 snapshot_delete
($vmid, $snapname, 1);
1663 sub snapshot_delete
{
1664 my ($vmid, $snapname, $force) = @_;
1670 my $updatefn = sub {
1672 $conf = load_config
($vmid);
1674 die "you can't delete a snapshot if vm is a template\n"
1675 if is_template
($conf);
1677 $snap = $conf->{snapshots
}->{$snapname};
1681 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1683 $snap->{snapstate
} = 'delete';
1685 write_config
($vmid, $conf);
1688 lock_container
($vmid, 10, $updatefn);
1690 my $storecfg = PVE
::Storage
::config
();
1692 my $del_snap = sub {
1696 if ($conf->{parent
} eq $snapname) {
1697 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1698 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1700 delete $conf->{parent
};
1704 delete $conf->{snapshots
}->{$snapname};
1706 write_config
($vmid, $conf);
1709 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1710 my $rootinfo = parse_ct_mountpoint
($rootfs);
1711 my $volid = $rootinfo->{volume
};
1714 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1718 if(!$err || ($err && $force)) {
1719 lock_container
($vmid, 10, $del_snap);
1721 die "Can't delete snapshot: $vmid $snapname $err\n";
1726 sub snapshot_rollback
{
1727 my ($vmid, $snapname) = @_;
1729 my $storecfg = PVE
::Storage
::config
();
1731 my $conf = load_config
($vmid);
1733 die "you can't rollback if vm is a template\n" if is_template
($conf);
1735 my $snap = $conf->{snapshots
}->{$snapname};
1737 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1739 my $rootfs = $snap->{rootfs
};
1740 my $rootinfo = parse_ct_mountpoint
($rootfs);
1741 my $volid = $rootinfo->{volume
};
1743 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1745 my $updatefn = sub {
1747 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1748 if $snap->{snapstate
};
1752 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1754 die "unable to rollback vm $vmid: vm is running\n"
1755 if check_running
($vmid);
1757 $conf->{lock} = 'rollback';
1761 # copy snapshot config to current config
1763 my $tmp_conf = $conf;
1764 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1765 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1766 delete $conf->{snaptime
};
1767 delete $conf->{snapname
};
1768 $conf->{parent
} = $snapname;
1770 write_config
($vmid, $conf);
1773 my $unlockfn = sub {
1774 delete $conf->{lock};
1775 write_config
($vmid, $conf);
1778 lock_container
($vmid, 10, $updatefn);
1780 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1782 lock_container
($vmid, 5, $unlockfn);
1785 sub template_create
{
1786 my ($vmid, $conf) = @_;
1788 my $storecfg = PVE
::Storage
::config
();
1790 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1791 my $volid = $rootinfo->{volume
};
1793 die "Template feature is not available for '$volid'\n"
1794 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1796 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1798 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1799 $rootinfo->{volume
} = $template_volid;
1800 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1802 write_config
($vmid, $conf);
1808 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1811 sub mountpoint_names
{
1814 my @names = ('rootfs');
1816 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1817 push @names, "mp$i";
1820 return $reverse ?
reverse @names : @names;
1823 # The container might have *different* symlinks than the host. realpath/abs_path
1824 # use the actual filesystem to resolve links.
1825 sub sanitize_mountpoint
{
1827 $mp = '/' . $mp; # we always start with a slash
1828 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1829 $mp =~ s
@/\./@@g; # collapse /./
1830 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1831 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1832 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1836 sub foreach_mountpoint_full
{
1837 my ($conf, $reverse, $func) = @_;
1839 foreach my $key (mountpoint_names
($reverse)) {
1840 my $value = $conf->{$key};
1841 next if !defined($value);
1842 my $mountpoint = parse_ct_mountpoint
($value);
1844 # just to be sure: rootfs is /
1845 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1846 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1848 $path = $mountpoint->{volume
};
1849 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1851 &$func($key, $mountpoint);
1855 sub foreach_mountpoint
{
1856 my ($conf, $func) = @_;
1858 foreach_mountpoint_full
($conf, 0, $func);
1861 sub foreach_mountpoint_reverse
{
1862 my ($conf, $func) = @_;
1864 foreach_mountpoint_full
($conf, 1, $func);
1867 sub check_ct_modify_config_perm
{
1868 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1870 return 1 if $authuser ne 'root@pam';
1872 foreach my $opt (@$key_list) {
1874 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1875 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1876 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1877 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1878 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1879 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1880 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1881 $opt eq 'searchdomain' || $opt eq 'hostname') {
1882 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1884 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1892 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1894 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1895 my $volid_list = get_vm_volumes
($conf);
1897 foreach_mountpoint_reverse
($conf, sub {
1898 my ($ms, $mountpoint) = @_;
1900 my $volid = $mountpoint->{volume
};
1901 my $mount = $mountpoint->{mp
};
1903 return if !$volid || !$mount;
1905 my $mount_path = "$rootdir/$mount";
1906 $mount_path =~ s!/+!/!g;
1908 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1911 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1924 my ($vmid, $storage_cfg, $conf) = @_;
1926 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1927 File
::Path
::make_path
($rootdir);
1929 my $volid_list = get_vm_volumes
($conf);
1930 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1933 foreach_mountpoint
($conf, sub {
1934 my ($ms, $mountpoint) = @_;
1936 my $volid = $mountpoint->{volume
};
1937 my $mount = $mountpoint->{mp
};
1939 return if !$volid || !$mount;
1941 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1942 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1943 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1945 die "unable to mount base volume - internal error" if $isBase;
1947 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
1951 warn "mounting container failed - $err";
1952 umount_all
($vmid, $storage_cfg, $conf, 1);
1959 sub mountpoint_mount_path
{
1960 my ($mountpoint, $storage_cfg, $snapname) = @_;
1962 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
1965 my $check_mount_path = sub {
1967 $path = File
::Spec-
>canonpath($path);
1968 my $real = Cwd
::realpath
($path);
1969 if ($real ne $path) {
1970 die "mount path modified by symlink: $path != $real";
1974 # use $rootdir = undef to just return the corresponding mount path
1975 sub mountpoint_mount
{
1976 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1978 my $volid = $mountpoint->{volume
};
1979 my $mount = $mountpoint->{mp
};
1981 return if !$volid || !$mount;
1985 if (defined($rootdir)) {
1986 $rootdir =~ s!/+$!!;
1987 $mount_path = "$rootdir/$mount";
1988 $mount_path =~ s!/+!/!g;
1989 &$check_mount_path($mount_path);
1990 File
::Path
::mkpath
($mount_path);
1993 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1995 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1999 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2000 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2002 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2003 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2005 if ($format eq 'subvol') {
2008 if ($scfg->{type
} eq 'zfspool') {
2009 my $path_arg = $path;
2010 $path_arg =~ s!^/+!!;
2011 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2013 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2016 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2019 return wantarray ?
($path, 0) : $path;
2020 } elsif ($format eq 'raw') {
2021 my $use_loopdev = 0;
2023 if ($scfg->{path
}) {
2024 push @extra_opts, '-o', 'loop';
2026 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2029 die "unsupported storage type '$scfg->{type}'\n";
2032 if ($isBase || defined($snapname)) {
2033 PVE
::Tools
::run_command
(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2035 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2038 return wantarray ?
($path, $use_loopdev) : $path;
2040 die "unsupported image format '$format'\n";
2042 } elsif ($volid =~ m
|^/dev/.+|) {
2043 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2044 return wantarray ?
($volid, 0) : $volid;
2045 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2046 &$check_mount_path($volid);
2047 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2048 return wantarray ?
($volid, 0) : $volid;
2051 die "unsupported storage";
2054 sub get_vm_volumes
{
2055 my ($conf, $excludes) = @_;
2059 foreach_mountpoint
($conf, sub {
2060 my ($ms, $mountpoint) = @_;
2062 return if $excludes && $ms eq $excludes;
2064 my $volid = $mountpoint->{volume
};
2066 return if !$volid || $volid =~ m
|^/|;
2068 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2071 push @$vollist, $volid;
2080 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2084 my ($storage_cfg, $volid) = @_;
2086 if ($volid =~ m!^/dev/.+!) {
2091 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2093 die "cannot format volume '$volid' with no storage\n" if !$storage;
2095 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2097 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2099 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2100 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2102 die "cannot format volume '$volid' (format == $format)\n"
2103 if $format ne 'raw';
2109 my ($storecfg, $vollist) = @_;
2111 foreach my $volid (@$vollist) {
2112 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2118 my ($storecfg, $vmid, $settings, $conf) = @_;
2123 foreach_mountpoint
($settings, sub {
2124 my ($ms, $mountpoint) = @_;
2126 my $volid = $mountpoint->{volume
};
2127 my $mp = $mountpoint->{mp
};
2129 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2131 return if !$storage;
2133 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2134 my ($storeid, $size_gb) = ($1, $2);
2136 my $size_kb = int(${size_gb
}*1024) * 1024;
2138 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2139 # fixme: use better naming ct-$vmid-disk-X.raw?
2141 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2143 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2145 format_disk
($storecfg, $volid);
2147 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2150 } elsif ($scfg->{type
} eq 'zfspool') {
2152 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2154 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2156 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2157 format_disk
($storecfg, $volid);
2159 } elsif ($scfg->{type
} eq 'rbd') {
2161 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2162 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2163 format_disk
($storecfg, $volid);
2165 die "unable to create containers on storage type '$scfg->{type}'\n";
2167 push @$vollist, $volid;
2168 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2169 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2171 # use specified/existing volid
2175 # free allocated images on error
2177 destroy_disks
($storecfg, $vollist);
2183 # bash completion helper
2185 sub complete_os_templates
{
2186 my ($cmdname, $pname, $cvalue) = @_;
2188 my $cfg = PVE
::Storage
::config
();
2192 if ($cvalue =~ m/^([^:]+):/) {
2196 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2197 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2200 foreach my $id (keys %$data) {
2201 foreach my $item (@{$data->{$id}}) {
2202 push @$res, $item->{volid
} if defined($item->{volid
});
2209 my $complete_ctid_full = sub {
2212 my $idlist = vmstatus
();
2214 my $active_hash = list_active_containers
();
2218 foreach my $id (keys %$idlist) {
2219 my $d = $idlist->{$id};
2220 if (defined($running)) {
2221 next if $d->{template
};
2222 next if $running && !$active_hash->{$id};
2223 next if !$running && $active_hash->{$id};
2232 return &$complete_ctid_full();
2235 sub complete_ctid_stopped
{
2236 return &$complete_ctid_full(0);
2239 sub complete_ctid_running
{
2240 return &$complete_ctid_full(1);