10 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
14 use PVE
::JSONSchema
qw(get_standard_option);
15 use PVE
::Tools
qw($IPV6RE $IPV4RE);
20 my $nodename = PVE
::INotify
::nodename
();
22 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
24 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
25 sub verify_lxc_network
{
26 my ($value, $noerr) = @_;
28 return $value if parse_lxc_network
($value);
30 return undef if $noerr;
32 die "unable to parse network setting\n";
35 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
36 sub verify_ct_mountpoint
{
37 my ($value, $noerr) = @_;
39 return $value if parse_ct_mountpoint
($value);
41 return undef if $noerr;
43 die "unable to parse CT mountpoint options\n";
46 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
47 type
=> 'string', format
=> 'pve-ct-mountpoint',
48 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
49 description
=> "Use volume as container root.",
57 description
=> "Lock/unlock the VM.",
58 enum
=> [qw(migrate backup snapshot rollback)],
63 description
=> "Specifies whether a VM will be started during system bootup.",
66 startup
=> get_standard_option
('pve-startup-order'),
70 description
=> "Enable/disable Template.",
76 enum
=> ['amd64', 'i386'],
77 description
=> "OS architecture type.",
83 enum
=> ['debian', 'ubuntu', 'centos'],
84 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
89 description
=> "Attach a console device (/dev/console) to the container.",
95 description
=> "Specify the number of tty available to the container",
103 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.",
111 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.",
119 description
=> "Amount of RAM for the VM in MB.",
126 description
=> "Amount of SWAP for the VM in MB.",
132 description
=> "Set a host name for the container.",
139 description
=> "Container description. Only used on the configuration web interface.",
144 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
149 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.",
151 rootfs
=> get_standard_option
('pve-ct-rootfs'),
154 type
=> 'string', format
=> 'pve-configid',
156 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
160 description
=> "Timestamp for snapshots.",
166 my $valid_lxc_conf_keys = {
170 'lxc.haltsignal' => 1,
171 'lxc.rebootsignal' => 1,
172 'lxc.stopsignal' => 1,
174 'lxc.network.type' => 1,
175 'lxc.network.flags' => 1,
176 'lxc.network.link' => 1,
177 'lxc.network.mtu' => 1,
178 'lxc.network.name' => 1,
179 'lxc.network.hwaddr' => 1,
180 'lxc.network.ipv4' => 1,
181 'lxc.network.ipv4.gateway' => 1,
182 'lxc.network.ipv6' => 1,
183 'lxc.network.ipv6.gateway' => 1,
184 'lxc.network.script.up' => 1,
185 'lxc.network.script.down' => 1,
187 'lxc.console.logfile' => 1,
190 'lxc.devttydir' => 1,
191 'lxc.hook.autodev' => 1,
195 'lxc.mount.entry' => 1,
196 'lxc.mount.auto' => 1,
198 'lxc.rootfs.mount' => 1,
199 'lxc.rootfs.options' => 1,
203 'lxc.aa_profile' => 1,
204 'lxc.aa_allow_incomplete' => 1,
205 'lxc.se_context' => 1,
208 'lxc.hook.pre-start' => 1,
209 'lxc.hook.pre-mount' => 1,
210 'lxc.hook.mount' => 1,
211 'lxc.hook.start' => 1,
212 'lxc.hook.post-stop' => 1,
213 'lxc.hook.clone' => 1,
214 'lxc.hook.destroy' => 1,
217 'lxc.start.auto' => 1,
218 'lxc.start.delay' => 1,
219 'lxc.start.order' => 1,
221 'lxc.environment' => 1,
228 my $MAX_LXC_NETWORKS = 10;
229 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
230 $confdesc->{"net$i"} = {
232 type
=> 'string', format
=> 'pve-lxc-network',
233 description
=> "Specifies network interfaces for the container.\n\n".
234 "The string should have the follow format:\n\n".
235 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
236 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
237 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
238 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
242 sub write_pct_config
{
243 my ($filename, $conf) = @_;
245 delete $conf->{snapstate
}; # just to be sure
247 my $generate_raw_config = sub {
252 # add description as comment to top of file
253 my $descr = $conf->{description
} || '';
254 foreach my $cl (split(/\n/, $descr)) {
255 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
258 foreach my $key (sort keys %$conf) {
259 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
260 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
261 $raw .= "$key: $conf->{$key}\n";
264 if (my $lxcconf = $conf->{lxc
}) {
265 foreach my $entry (@$lxcconf) {
266 my ($k, $v) = @$entry;
274 my $raw = &$generate_raw_config($conf);
276 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
277 $raw .= "\n[$snapname]\n";
278 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
285 my ($key, $value) = @_;
287 die "unknown setting '$key'\n" if !$confdesc->{$key};
289 my $type = $confdesc->{$key}->{type
};
291 if (!defined($value)) {
292 die "got undefined value\n";
295 if ($value =~ m/[\n\r]/) {
296 die "property contains a line feed\n";
299 if ($type eq 'boolean') {
300 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
301 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
302 die "type check ('boolean') failed - got '$value'\n";
303 } elsif ($type eq 'integer') {
304 return int($1) if $value =~ m/^(\d+)$/;
305 die "type check ('integer') failed - got '$value'\n";
306 } elsif ($type eq 'number') {
307 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
308 die "type check ('number') failed - got '$value'\n";
309 } elsif ($type eq 'string') {
310 if (my $fmt = $confdesc->{$key}->{format
}) {
311 PVE
::JSONSchema
::check_format
($fmt, $value);
320 sub parse_pct_config
{
321 my ($filename, $raw) = @_;
323 return undef if !defined($raw);
326 digest
=> Digest
::SHA
::sha1_hex
($raw),
330 $filename =~ m
|/lxc/(\d
+).conf
$|
331 || die "got strange filename '$filename'";
339 my @lines = split(/\n/, $raw);
340 foreach my $line (@lines) {
341 next if $line =~ m/^\s*$/;
343 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
345 $conf->{description
} = $descr if $descr;
347 $conf = $res->{snapshots
}->{$section} = {};
351 if ($line =~ m/^\#(.*)\s*$/) {
352 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
356 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
359 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
360 push @{$conf->{lxc
}}, [$key, $value];
362 warn "vm $vmid - unable to parse config: $line\n";
364 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
365 $descr .= PVE
::Tools
::decode_text
($2);
366 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
367 $conf->{snapstate
} = $1;
368 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
371 eval { $value = check_type
($key, $value); };
372 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
373 $conf->{$key} = $value;
375 warn "vm $vmid - unable to parse config: $line\n";
379 $conf->{description
} = $descr if $descr;
381 delete $res->{snapstate
}; # just to be sure
387 my $vmlist = PVE
::Cluster
::get_vmlist
();
389 return $res if !$vmlist || !$vmlist->{ids
};
390 my $ids = $vmlist->{ids
};
392 foreach my $vmid (keys %$ids) {
393 next if !$vmid; # skip CT0
394 my $d = $ids->{$vmid};
395 next if !$d->{node
} || $d->{node
} ne $nodename;
396 next if !$d->{type
} || $d->{type
} ne 'lxc';
397 $res->{$vmid}->{type
} = 'lxc';
402 sub cfs_config_path
{
403 my ($vmid, $node) = @_;
405 $node = $nodename if !$node;
406 return "nodes/$node/lxc/$vmid.conf";
410 my ($vmid, $node) = @_;
412 my $cfspath = cfs_config_path
($vmid, $node);
413 return "/etc/pve/$cfspath";
419 my $cfspath = cfs_config_path
($vmid);
421 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
422 die "container $vmid does not exists\n" if !defined($conf);
428 my ($vmid, $conf) = @_;
430 my $dir = "/etc/pve/nodes/$nodename/lxc";
433 write_config
($vmid, $conf);
439 unlink config_file
($vmid, $nodename);
443 my ($vmid, $conf) = @_;
445 my $cfspath = cfs_config_path
($vmid);
447 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
450 # flock: we use one file handle per process, so lock file
451 # can be called multiple times and succeeds for the same process.
453 my $lock_handles = {};
454 my $lockdir = "/run/lock/lxc";
459 return "$lockdir/pve-config-{$vmid}.lock";
463 my ($vmid, $timeout) = @_;
465 $timeout = 10 if !$timeout;
468 my $filename = lock_filename
($vmid);
470 mkdir $lockdir if !-d
$lockdir;
472 my $lock_func = sub {
473 if (!$lock_handles->{$$}->{$filename}) {
474 my $fh = new IO
::File
(">>$filename") ||
475 die "can't open file - $!\n";
476 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
479 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
480 print STDERR
"trying to aquire lock...";
483 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
484 # try again on EINTR (see bug #273)
485 if ($success || ($! != EINTR
)) {
490 print STDERR
" failed\n";
491 die "can't aquire lock - $!\n";
494 $lock_handles->{$$}->{$filename}->{refcount
}++;
496 print STDERR
" OK\n";
500 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
503 die "can't lock file '$filename' - $err";
510 my $filename = lock_filename
($vmid);
512 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
513 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
514 if ($refcount <= 0) {
515 $lock_handles->{$$}->{$filename} = undef;
522 my ($vmid, $timeout, $code, @param) = @_;
526 lock_aquire
($vmid, $timeout);
527 eval { $res = &$code(@param) };
539 return defined($confdesc->{$name});
542 # add JSON properties for create and set function
543 sub json_config_properties
{
546 foreach my $opt (keys %$confdesc) {
547 next if $opt eq 'parent' || $opt eq 'snaptime';
548 next if $prop->{$opt};
549 $prop->{$opt} = $confdesc->{$opt};
555 sub json_config_properties_no_rootfs
{
558 foreach my $opt (keys %$confdesc) {
559 next if $prop->{$opt};
560 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
561 $prop->{$opt} = $confdesc->{$opt};
567 # container status helpers
569 sub list_active_containers
{
571 my $filename = "/proc/net/unix";
573 # similar test is used by lcxcontainers.c: list_active_containers
576 my $fh = IO
::File-
>new ($filename, "r");
579 while (defined(my $line = <$fh>)) {
580 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
582 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
593 # warning: this is slow
597 my $active_hash = list_active_containers
();
599 return 1 if defined($active_hash->{$vmid});
604 sub get_container_disk_usage
{
607 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
617 if (my ($fsid, $total, $used, $avail) = $line =~
618 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
626 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
635 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
637 my $active_hash = list_active_containers
();
639 foreach my $vmid (keys %$list) {
640 my $d = $list->{$vmid};
642 my $running = defined($active_hash->{$vmid});
644 $d->{status
} = $running ?
'running' : 'stopped';
646 my $cfspath = cfs_config_path
($vmid);
647 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
649 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
650 $d->{name
} =~ s/[\s]//g;
652 $d->{cpus
} = $conf->{cpulimit
} // 0;
655 my $res = get_container_disk_usage
($vmid);
656 $d->{disk
} = $res->{used
};
657 $d->{maxdisk
} = $res->{total
};
660 # use 4GB by default ??
661 if (my $rootfs = $conf->{rootfs
}) {
662 my $rootinfo = parse_ct_mountpoint
($rootfs);
663 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
665 $d->{maxdisk
} = 4*1024*1024*1024;
671 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
672 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
683 $d->{template
} = is_template
($conf);
686 foreach my $vmid (keys %$list) {
687 my $d = $list->{$vmid};
688 next if $d->{status
} ne 'running';
690 $d->{uptime
} = 100; # fixme:
692 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
693 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
695 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
696 my @bytes = split(/\n/, $blkio_bytes);
697 foreach my $byte (@bytes) {
698 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
699 $d->{diskread
} = $2 if $key eq 'Read';
700 $d->{diskwrite
} = $2 if $key eq 'Write';
708 my $parse_size = sub {
711 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
712 my ($size, $unit) = ($1, $3);
715 $size = $size * 1024;
716 } elsif ($unit eq 'M') {
717 $size = $size * 1024 * 1024;
718 } elsif ($unit eq 'G') {
719 $size = $size * 1024 * 1024 * 1024;
725 sub parse_ct_mountpoint
{
732 foreach my $p (split (/,/, $data)) {
733 next if $p =~ m/^\s*$/;
735 if ($p =~ m/^(volume|backup|size)=(.+)$/) {
736 my ($k, $v) = ($1, $2);
737 return undef if defined($res->{$k});
740 if (!$res->{volume
} && $p !~ m/=/) {
748 return undef if !$res->{volume
};
750 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
753 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
759 sub print_ct_mountpoint
{
764 die "missing volume\n" if !$info->{volume
};
766 foreach my $o ('size', 'backup') {
767 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
770 return "$info->{volume}$opts";
773 sub print_lxc_network
{
776 die "no network name defined\n" if !$net->{name
};
778 my $res = "name=$net->{name}";
780 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
781 next if !defined($net->{$k});
782 $res .= ",$k=$net->{$k}";
788 sub parse_lxc_network
{
793 return $res if !$data;
795 foreach my $pv (split (/,/, $data)) {
796 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
803 $res->{type
} = 'veth';
804 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
809 sub read_cgroup_value
{
810 my ($group, $vmid, $name, $full) = @_;
812 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
814 return PVE
::Tools
::file_get_contents
($path) if $full;
816 return PVE
::Tools
::file_read_firstline
($path);
819 sub write_cgroup_value
{
820 my ($group, $vmid, $name, $value) = @_;
822 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
823 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
827 sub find_lxc_console_pids
{
831 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
834 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
837 my @args = split(/\0/, $cmdline);
839 # serach for lxc-console -n <vmid>
840 return if scalar(@args) != 3;
841 return if $args[1] ne '-n';
842 return if $args[2] !~ m/^\d+$/;
843 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
847 push @{$res->{$vmid}}, $pid;
859 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
861 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
863 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
868 my $ipv4_reverse_mask = [
904 # Note: we cannot use Net:IP, because that only allows strict
906 sub parse_ipv4_cidr
{
907 my ($cidr, $noerr) = @_;
909 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
910 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
913 return undef if $noerr;
915 die "unable to parse ipv4 address/mask\n";
921 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
924 sub update_lxc_config
{
925 my ($storage_cfg, $vmid, $conf) = @_;
927 my $dir = "/var/lib/lxc/$vmid";
929 if ($conf->{template
}) {
931 unlink "$dir/config";
938 die "missing 'arch' - internal error" if !$conf->{arch
};
939 $raw .= "lxc.arch = $conf->{arch}\n";
941 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
942 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
943 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
948 if (!has_dev_console
($conf)) {
949 $raw .= "lxc.console = none\n";
950 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
953 my $ttycount = get_tty_count
($conf);
954 $raw .= "lxc.tty = $ttycount\n";
956 my $utsname = $conf->{hostname
} || "CT$vmid";
957 $raw .= "lxc.utsname = $utsname\n";
959 my $memory = $conf->{memory
} || 512;
960 my $swap = $conf->{swap
} // 0;
962 my $lxcmem = int($memory*1024*1024);
963 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
965 my $lxcswap = int(($memory + $swap)*1024*1024);
966 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
968 if (my $cpulimit = $conf->{cpulimit
}) {
969 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
970 my $value = int(100000*$cpulimit);
971 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
974 my $shares = $conf->{cpuunits
} || 1024;
975 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
977 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
978 my $volid = $rootinfo->{volume
};
979 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
981 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
982 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
984 die "unable to use template as rootfs\n" if $isBase;
986 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
987 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
989 if ($format eq 'subvol') {
990 $raw .= "lxc.rootfs = $path\n";
991 } elsif ($format eq 'raw') {
993 $raw .= "lxc.rootfs = loop:$path\n";
994 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
995 $raw .= "lxc.rootfs = $path\n";
997 die "unsupported storage type '$scfg->{type}'\n";
1000 die "unsupported image format '$format'\n";
1004 foreach my $k (keys %$conf) {
1005 next if $k !~ m/^net(\d+)$/;
1007 my $d = parse_lxc_network
($conf->{$k});
1009 $raw .= "lxc.network.type = veth\n";
1010 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1011 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1012 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1013 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1016 if (my $lxcconf = $conf->{lxc
}) {
1017 foreach my $entry (@$lxcconf) {
1018 my ($k, $v) = @$entry;
1019 $netcount++ if $k eq 'lxc.network.type';
1020 $raw .= "$k = $v\n";
1024 $raw .= "lxc.network.type = empty\n" if !$netcount;
1026 File
::Path
::mkpath
("$dir/rootfs");
1028 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1031 # verify and cleanup nameserver list (replace \0 with ' ')
1032 sub verify_nameserver_list
{
1033 my ($nameserver_list) = @_;
1036 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1037 PVE
::JSONSchema
::pve_verify_ip
($server);
1038 push @list, $server;
1041 return join(' ', @list);
1044 sub verify_searchdomain_list
{
1045 my ($searchdomain_list) = @_;
1048 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1049 # todo: should we add checks for valid dns domains?
1050 push @list, $server;
1053 return join(' ', @list);
1056 sub update_pct_config
{
1057 my ($vmid, $conf, $running, $param, $delete) = @_;
1063 my $pid = find_lxc_pid
($vmid);
1064 $rootdir = "/proc/$pid/root";
1067 if (defined($delete)) {
1068 foreach my $opt (@$delete) {
1069 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1070 die "unable to delete required option '$opt'\n";
1071 } elsif ($opt eq 'swap') {
1072 delete $conf->{$opt};
1073 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1074 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1075 delete $conf->{$opt};
1076 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1077 $opt eq 'tty' || $opt eq 'console') {
1078 delete $conf->{$opt};
1079 push @nohotplug, $opt;
1081 } elsif ($opt =~ m/^net(\d)$/) {
1082 delete $conf->{$opt};
1085 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1089 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1093 # There's no separate swap size to configure, there's memory and "total"
1094 # memory (iow. memory+swap). This means we have to change them together.
1095 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1096 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1097 if (defined($wanted_memory) || defined($wanted_swap)) {
1099 $wanted_memory //= ($conf->{memory
} || 512);
1100 $wanted_swap //= ($conf->{swap
} || 0);
1102 my $total = $wanted_memory + $wanted_swap;
1104 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1105 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1107 $conf->{memory
} = $wanted_memory;
1108 $conf->{swap
} = $wanted_swap;
1110 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1113 foreach my $opt (keys %$param) {
1114 my $value = $param->{$opt};
1115 if ($opt eq 'hostname') {
1116 $conf->{$opt} = $value;
1117 } elsif ($opt eq 'onboot') {
1118 $conf->{$opt} = $value ?
1 : 0;
1119 } elsif ($opt eq 'startup') {
1120 $conf->{$opt} = $value;
1121 } elsif ($opt eq 'tty' || $opt eq 'console') {
1122 $conf->{$opt} = $value;
1123 push @nohotplug, $opt;
1125 } elsif ($opt eq 'nameserver') {
1126 my $list = verify_nameserver_list
($value);
1127 $conf->{$opt} = $list;
1128 push @nohotplug, $opt;
1130 } elsif ($opt eq 'searchdomain') {
1131 my $list = verify_searchdomain_list
($value);
1132 $conf->{$opt} = $list;
1133 push @nohotplug, $opt;
1135 } elsif ($opt eq 'cpulimit') {
1136 $conf->{$opt} = $value;
1137 push @nohotplug, $opt; # fixme: hotplug
1139 } elsif ($opt eq 'cpuunits') {
1140 $conf->{$opt} = $value;
1141 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1142 } elsif ($opt eq 'description') {
1143 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1144 } elsif ($opt =~ m/^net(\d+)$/) {
1146 my $net = parse_lxc_network
($value);
1148 $conf->{$opt} = print_lxc_network
($net);
1150 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1153 die "implement me: $opt";
1155 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1158 if ($running && scalar(@nohotplug)) {
1159 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1163 sub has_dev_console
{
1166 return !(defined($conf->{console
}) && !$conf->{console
});
1172 return $conf->{tty
} // $confdesc->{tty
}->{default};
1175 sub get_primary_ips
{
1178 # return data from net0
1180 return undef if !defined($conf->{net0
});
1181 my $net = parse_lxc_network
($conf->{net0
});
1183 my $ipv4 = $net->{ip
};
1185 if ($ipv4 =~ /^(dhcp|manual)$/) {
1191 my $ipv6 = $net->{ip6
};
1193 if ($ipv6 =~ /^(dhcp|manual)$/) {
1200 return ($ipv4, $ipv6);
1204 sub destroy_lxc_container
{
1205 my ($storage_cfg, $vmid, $conf) = @_;
1207 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1208 if (defined($rootinfo->{volume
})) {
1209 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1210 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1212 rmdir "/var/lib/lxc/$vmid/rootfs";
1213 unlink "/var/lib/lxc/$vmid/config";
1214 rmdir "/var/lib/lxc/$vmid";
1215 destroy_config
($vmid);
1217 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1218 #PVE::Tools::run_command($cmd);
1221 sub vm_stop_cleanup
{
1222 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1226 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1227 PVE
::Storage
::deactivate_volumes
($storeage_cfg, [$rootinfo->{volume
}]);
1230 warn $@ if $@; # avoid errors - just warn
1233 my $safe_num_ne = sub {
1236 return 0 if !defined($a) && !defined($b);
1237 return 1 if !defined($a);
1238 return 1 if !defined($b);
1243 my $safe_string_ne = sub {
1246 return 0 if !defined($a) && !defined($b);
1247 return 1 if !defined($a);
1248 return 1 if !defined($b);
1254 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1256 if ($newnet->{type
} ne 'veth') {
1257 # for when there are physical interfaces
1258 die "cannot update interface of type $newnet->{type}";
1261 my $veth = "veth${vmid}i${netid}";
1262 my $eth = $newnet->{name
};
1264 if (my $oldnetcfg = $conf->{$opt}) {
1265 my $oldnet = parse_lxc_network
($oldnetcfg);
1267 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1268 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1270 PVE
::Network
::veth_delete
($veth);
1271 delete $conf->{$opt};
1272 PVE
::LXC
::write_config
($vmid, $conf);
1274 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1276 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1277 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1278 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1280 if ($oldnet->{bridge
}) {
1281 PVE
::Network
::tap_unplug
($veth);
1282 foreach (qw(bridge tag firewall)) {
1283 delete $oldnet->{$_};
1285 $conf->{$opt} = print_lxc_network
($oldnet);
1286 PVE
::LXC
::write_config
($vmid, $conf);
1289 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1290 foreach (qw(bridge tag firewall)) {
1291 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1293 $conf->{$opt} = print_lxc_network
($oldnet);
1294 PVE
::LXC
::write_config
($vmid, $conf);
1297 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1300 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1304 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1306 my $veth = "veth${vmid}i${netid}";
1307 my $vethpeer = $veth . "p";
1308 my $eth = $newnet->{name
};
1310 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1311 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1313 # attach peer in container
1314 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1315 PVE
::Tools
::run_command
($cmd);
1317 # link up peer in container
1318 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1319 PVE
::Tools
::run_command
($cmd);
1321 my $done = { type
=> 'veth' };
1322 foreach (qw(bridge tag firewall hwaddr name)) {
1323 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1325 $conf->{$opt} = print_lxc_network
($done);
1327 PVE
::LXC
::write_config
($vmid, $conf);
1330 sub update_ipconfig
{
1331 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1333 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1335 my $optdata = parse_lxc_network
($conf->{$opt});
1339 my $cmdargs = shift;
1340 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1342 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1344 my $change_ip_config = sub {
1345 my ($ipversion) = @_;
1347 my $family_opt = "-$ipversion";
1348 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1349 my $gw= "gw$suffix";
1350 my $ip= "ip$suffix";
1352 my $newip = $newnet->{$ip};
1353 my $newgw = $newnet->{$gw};
1354 my $oldip = $optdata->{$ip};
1356 my $change_ip = &$safe_string_ne($oldip, $newip);
1357 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1359 return if !$change_ip && !$change_gw;
1361 # step 1: add new IP, if this fails we cancel
1362 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1363 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1370 # step 2: replace gateway
1371 # If this fails we delete the added IP and cancel.
1372 # If it succeeds we save the config and delete the old IP, ignoring
1373 # errors. The config is then saved.
1374 # Note: 'ip route replace' can add
1377 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1380 # the route was not replaced, the old IP is still available
1381 # rollback (delete new IP) and cancel
1383 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1384 warn $@ if $@; # no need to die here
1389 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1390 # if the route was not deleted, the guest might have deleted it manually
1396 # from this point on we save the configuration
1397 # step 3: delete old IP ignoring errors
1398 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1399 # We need to enable promote_secondaries, otherwise our newly added
1400 # address will be removed along with the old one.
1403 if ($ipversion == 4) {
1404 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1405 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1406 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1408 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1410 warn $@ if $@; # no need to die here
1412 if ($ipversion == 4) {
1413 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1417 foreach my $property ($ip, $gw) {
1418 if ($newnet->{$property}) {
1419 $optdata->{$property} = $newnet->{$property};
1421 delete $optdata->{$property};
1424 $conf->{$opt} = print_lxc_network
($optdata);
1425 PVE
::LXC
::write_config
($vmid, $conf);
1426 $lxc_setup->setup_network($conf);
1429 &$change_ip_config(4);
1430 &$change_ip_config(6);
1434 # Internal snapshots
1436 # NOTE: Snapshot create/delete involves several non-atomic
1437 # action, and can take a long time.
1438 # So we try to avoid locking the file and use 'lock' variable
1439 # inside the config file instead.
1441 my $snapshot_copy_config = sub {
1442 my ($source, $dest) = @_;
1444 foreach my $k (keys %$source) {
1445 next if $k eq 'snapshots';
1446 next if $k eq 'snapstate';
1447 next if $k eq 'snaptime';
1448 next if $k eq 'vmstate';
1449 next if $k eq 'lock';
1450 next if $k eq 'digest';
1451 next if $k eq 'description';
1453 $dest->{$k} = $source->{$k};
1457 my $snapshot_prepare = sub {
1458 my ($vmid, $snapname, $comment) = @_;
1462 my $updatefn = sub {
1464 my $conf = load_config
($vmid);
1466 die "you can't take a snapshot if it's a template\n"
1467 if is_template
($conf);
1471 $conf->{lock} = 'snapshot';
1473 die "snapshot name '$snapname' already used\n"
1474 if defined($conf->{snapshots
}->{$snapname});
1476 my $storecfg = PVE
::Storage
::config
();
1477 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1479 $snap = $conf->{snapshots
}->{$snapname} = {};
1481 &$snapshot_copy_config($conf, $snap);
1483 $snap->{'snapstate'} = "prepare";
1484 $snap->{'snaptime'} = time();
1485 $snap->{'description'} = $comment if $comment;
1486 $conf->{snapshots
}->{$snapname} = $snap;
1488 PVE
::LXC
::write_config
($vmid, $conf);
1491 lock_container
($vmid, 10, $updatefn);
1496 my $snapshot_commit = sub {
1497 my ($vmid, $snapname) = @_;
1499 my $updatefn = sub {
1501 my $conf = load_config
($vmid);
1503 die "missing snapshot lock\n"
1504 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1506 die "snapshot '$snapname' does not exist\n"
1507 if !defined($conf->{snapshots
}->{$snapname});
1509 die "wrong snapshot state\n"
1510 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1511 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1513 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1514 delete $conf->{lock};
1515 $conf->{parent
} = $snapname;
1517 PVE
::LXC
::write_config
($vmid, $conf);
1520 lock_container
($vmid, 10 ,$updatefn);
1524 my ($feature, $conf, $storecfg, $snapname) = @_;
1526 #Fixme add other drives if necessary.
1529 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1530 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1532 return $err ?
0 : 1;
1535 sub snapshot_create
{
1536 my ($vmid, $snapname, $comment) = @_;
1538 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1540 my $conf = load_config
($vmid);
1542 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1543 my $running = check_running
($vmid);
1546 PVE
::Tools
::run_command
($cmd);
1549 my $storecfg = PVE
::Storage
::config
();
1550 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1551 my $volid = $rootinfo->{volume
};
1553 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1555 PVE
::Tools
::run_command
($cmd);
1558 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1559 &$snapshot_commit($vmid, $snapname);
1562 snapshot_delete
($vmid, $snapname, 1);
1567 sub snapshot_delete
{
1568 my ($vmid, $snapname, $force) = @_;
1574 my $updatefn = sub {
1576 $conf = load_config
($vmid);
1578 die "you can't delete a snapshot if vm is a template\n"
1579 if is_template
($conf);
1581 $snap = $conf->{snapshots
}->{$snapname};
1585 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1587 $snap->{snapstate
} = 'delete';
1589 PVE
::LXC
::write_config
($vmid, $conf);
1592 lock_container
($vmid, 10, $updatefn);
1594 my $storecfg = PVE
::Storage
::config
();
1596 my $del_snap = sub {
1600 if ($conf->{parent
} eq $snapname) {
1601 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1602 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1604 delete $conf->{parent
};
1608 delete $conf->{snapshots
}->{$snapname};
1610 PVE
::LXC
::write_config
($vmid, $conf);
1613 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1614 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1615 my $volid = $rootinfo->{volume
};
1618 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1622 if(!$err || ($err && $force)) {
1623 lock_container
($vmid, 10, $del_snap);
1625 die "Can't delete snapshot: $vmid $snapname $err\n";
1630 sub snapshot_rollback
{
1631 my ($vmid, $snapname) = @_;
1633 my $storecfg = PVE
::Storage
::config
();
1635 my $conf = load_config
($vmid);
1637 die "you can't rollback if vm is a template\n" if is_template
($conf);
1639 my $snap = $conf->{snapshots
}->{$snapname};
1641 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1643 my $rootfs = $snap->{rootfs
};
1644 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1645 my $volid = $rootinfo->{volume
};
1647 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1649 my $updatefn = sub {
1651 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1652 if $snap->{snapstate
};
1656 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1658 die "unable to rollback vm $vmid: vm is running\n"
1659 if check_running
($vmid);
1661 $conf->{lock} = 'rollback';
1665 # copy snapshot config to current config
1667 my $tmp_conf = $conf;
1668 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1669 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1670 delete $conf->{snaptime
};
1671 delete $conf->{snapname
};
1672 $conf->{parent
} = $snapname;
1674 PVE
::LXC
::write_config
($vmid, $conf);
1677 my $unlockfn = sub {
1678 delete $conf->{lock};
1679 PVE
::LXC
::write_config
($vmid, $conf);
1682 lock_container
($vmid, 10, $updatefn);
1684 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1686 lock_container
($vmid, 5, $unlockfn);
1689 sub template_create
{
1690 my ($vmid, $conf) = @_;
1692 my $storecfg = PVE
::Storage
::config
();
1694 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1695 my $volid = $rootinfo->{volume
};
1697 die "Template feature is not available for '$volid'\n"
1698 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1700 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1702 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1703 $rootinfo->{volume
} = $template_volid;
1704 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1706 write_config
($vmid, $conf);
1712 return 1 if defined $conf->{template
} && $conf->{template
} == 1;