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. You can use special '<storage>:<size>' syntax for create/restore, where size specifies the disk size in GB (for example 'local:5.5' to create 5.5GB image on storage 'local').",
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 enum
=> ['amd64', 'i386'],
71 description
=> "OS architecture type.",
77 enum
=> ['debian', 'ubuntu', 'centos'],
78 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
83 description
=> "Specify the number of tty available to the container",
91 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.",
99 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.",
107 description
=> "Amount of RAM for the VM in MB.",
114 description
=> "Amount of SWAP for the VM in MB.",
120 description
=> "Set a host name for the container.",
127 description
=> "Container description. Only used on the configuration web interface.",
132 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
137 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.",
139 rootfs
=> get_standard_option
('pve-ct-rootfs'),
142 type
=> 'string', format
=> 'pve-configid',
144 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
148 description
=> "Timestamp for snapshots.",
154 my $valid_lxc_conf_keys = {
158 'lxc.haltsignal' => 1,
159 'lxc.rebootsignal' => 1,
160 'lxc.stopsignal' => 1,
162 'lxc.network.type' => 1,
163 'lxc.network.flags' => 1,
164 'lxc.network.link' => 1,
165 'lxc.network.mtu' => 1,
166 'lxc.network.name' => 1,
167 'lxc.network.hwaddr' => 1,
168 'lxc.network.ipv4' => 1,
169 'lxc.network.ipv4.gateway' => 1,
170 'lxc.network.ipv6' => 1,
171 'lxc.network.ipv6.gateway' => 1,
172 'lxc.network.script.up' => 1,
173 'lxc.network.script.down' => 1,
175 'lxc.console.logfile' => 1,
178 'lxc.devttydir' => 1,
179 'lxc.hook.autodev' => 1,
183 'lxc.mount.entry' => 1,
184 'lxc.mount.auto' => 1,
186 'lxc.rootfs.mount' => 1,
187 'lxc.rootfs.options' => 1,
191 'lxc.aa_profile' => 1,
192 'lxc.aa_allow_incomplete' => 1,
193 'lxc.se_context' => 1,
196 'lxc.hook.pre-start' => 1,
197 'lxc.hook.pre-mount' => 1,
198 'lxc.hook.mount' => 1,
199 'lxc.hook.start' => 1,
200 'lxc.hook.post-stop' => 1,
201 'lxc.hook.clone' => 1,
202 'lxc.hook.destroy' => 1,
205 'lxc.start.auto' => 1,
206 'lxc.start.delay' => 1,
207 'lxc.start.order' => 1,
209 'lxc.environment' => 1,
216 my $MAX_LXC_NETWORKS = 10;
217 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
218 $confdesc->{"net$i"} = {
220 type
=> 'string', format
=> 'pve-lxc-network',
221 description
=> "Specifies network interfaces for the container.\n\n".
222 "The string should have the follow format:\n\n".
223 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
224 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
225 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
226 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
230 sub write_pct_config
{
231 my ($filename, $conf) = @_;
233 delete $conf->{snapstate
}; # just to be sure
235 my $generate_raw_config = sub {
240 # add description as comment to top of file
241 my $descr = $conf->{description
} || '';
242 foreach my $cl (split(/\n/, $descr)) {
243 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
246 foreach my $key (sort keys %$conf) {
247 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
248 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
249 $raw .= "$key: $conf->{$key}\n";
252 if (my $lxcconf = $conf->{lxc
}) {
253 foreach my $entry (@$lxcconf) {
254 my ($k, $v) = @$entry;
262 my $raw = &$generate_raw_config($conf);
264 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
265 $raw .= "\n[$snapname]\n";
266 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
273 my ($key, $value) = @_;
275 die "unknown setting '$key'\n" if !$confdesc->{$key};
277 my $type = $confdesc->{$key}->{type
};
279 if (!defined($value)) {
280 die "got undefined value\n";
283 if ($value =~ m/[\n\r]/) {
284 die "property contains a line feed\n";
287 if ($type eq 'boolean') {
288 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
289 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
290 die "type check ('boolean') failed - got '$value'\n";
291 } elsif ($type eq 'integer') {
292 return int($1) if $value =~ m/^(\d+)$/;
293 die "type check ('integer') failed - got '$value'\n";
294 } elsif ($type eq 'number') {
295 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
296 die "type check ('number') failed - got '$value'\n";
297 } elsif ($type eq 'string') {
298 if (my $fmt = $confdesc->{$key}->{format
}) {
299 PVE
::JSONSchema
::check_format
($fmt, $value);
308 sub parse_pct_config
{
309 my ($filename, $raw) = @_;
311 return undef if !defined($raw);
314 digest
=> Digest
::SHA
::sha1_hex
($raw),
318 $filename =~ m
|/lxc/(\d
+).conf
$|
319 || die "got strange filename '$filename'";
327 my @lines = split(/\n/, $raw);
328 foreach my $line (@lines) {
329 next if $line =~ m/^\s*$/;
331 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
333 $conf->{description
} = $descr if $descr;
335 $conf = $res->{snapshots
}->{$section} = {};
339 if ($line =~ m/^\#(.*)\s*$/) {
340 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
344 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(\S.*)\s*$/) {
347 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
348 push @{$conf->{lxc
}}, [$key, $value];
350 warn "vm $vmid - unable to parse config: $line\n";
352 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
353 $descr .= PVE
::Tools
::decode_text
($2);
354 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
355 $conf->{snapstate
} = $1;
356 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
359 eval { $value = check_type
($key, $value); };
360 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
361 $conf->{$key} = $value;
363 warn "vm $vmid - unable to parse config: $line\n";
367 $conf->{description
} = $descr if $descr;
369 delete $res->{snapstate
}; # just to be sure
375 my $vmlist = PVE
::Cluster
::get_vmlist
();
377 return $res if !$vmlist || !$vmlist->{ids
};
378 my $ids = $vmlist->{ids
};
380 foreach my $vmid (keys %$ids) {
381 next if !$vmid; # skip CT0
382 my $d = $ids->{$vmid};
383 next if !$d->{node
} || $d->{node
} ne $nodename;
384 next if !$d->{type
} || $d->{type
} ne 'lxc';
385 $res->{$vmid}->{type
} = 'lxc';
390 sub cfs_config_path
{
391 my ($vmid, $node) = @_;
393 $node = $nodename if !$node;
394 return "nodes/$node/lxc/$vmid.conf";
398 my ($vmid, $node) = @_;
400 my $cfspath = cfs_config_path
($vmid, $node);
401 return "/etc/pve/$cfspath";
407 my $cfspath = cfs_config_path
($vmid);
409 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
410 die "container $vmid does not exists\n" if !defined($conf);
416 my ($vmid, $conf) = @_;
418 my $dir = "/etc/pve/nodes/$nodename/lxc";
421 write_config
($vmid, $conf);
427 unlink config_file
($vmid, $nodename);
431 my ($vmid, $conf) = @_;
433 my $cfspath = cfs_config_path
($vmid);
435 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
438 # flock: we use one file handle per process, so lock file
439 # can be called multiple times and succeeds for the same process.
441 my $lock_handles = {};
442 my $lockdir = "/run/lock/lxc";
447 return "$lockdir/pve-config-{$vmid}.lock";
451 my ($vmid, $timeout) = @_;
453 $timeout = 10 if !$timeout;
456 my $filename = lock_filename
($vmid);
458 mkdir $lockdir if !-d
$lockdir;
460 my $lock_func = sub {
461 if (!$lock_handles->{$$}->{$filename}) {
462 my $fh = new IO
::File
(">>$filename") ||
463 die "can't open file - $!\n";
464 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
467 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
468 print STDERR
"trying to aquire lock...";
471 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
472 # try again on EINTR (see bug #273)
473 if ($success || ($! != EINTR
)) {
478 print STDERR
" failed\n";
479 die "can't aquire lock - $!\n";
482 $lock_handles->{$$}->{$filename}->{refcount
}++;
484 print STDERR
" OK\n";
488 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
491 die "can't lock file '$filename' - $err";
498 my $filename = lock_filename
($vmid);
500 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
501 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
502 if ($refcount <= 0) {
503 $lock_handles->{$$}->{$filename} = undef;
510 my ($vmid, $timeout, $code, @param) = @_;
514 lock_aquire
($vmid, $timeout);
515 eval { $res = &$code(@param) };
527 return defined($confdesc->{$name});
530 # add JSON properties for create and set function
531 sub json_config_properties
{
534 foreach my $opt (keys %$confdesc) {
535 next if $opt eq 'parent' || $opt eq 'snaptime';
536 next if $prop->{$opt};
537 $prop->{$opt} = $confdesc->{$opt};
543 sub json_config_properties_no_rootfs
{
546 foreach my $opt (keys %$confdesc) {
547 next if $prop->{$opt};
548 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
549 $prop->{$opt} = $confdesc->{$opt};
555 # container status helpers
557 sub list_active_containers
{
559 my $filename = "/proc/net/unix";
561 # similar test is used by lcxcontainers.c: list_active_containers
564 my $fh = IO
::File-
>new ($filename, "r");
567 while (defined(my $line = <$fh>)) {
568 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
570 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
581 # warning: this is slow
585 my $active_hash = list_active_containers
();
587 return 1 if defined($active_hash->{$vmid});
592 sub get_container_disk_usage
{
595 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
605 if (my ($fsid, $total, $used, $avail) = $line =~
606 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
614 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
623 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
625 my $active_hash = list_active_containers
();
627 foreach my $vmid (keys %$list) {
628 my $d = $list->{$vmid};
630 my $running = defined($active_hash->{$vmid});
632 $d->{status
} = $running ?
'running' : 'stopped';
634 my $cfspath = cfs_config_path
($vmid);
635 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
637 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
638 $d->{name
} =~ s/[\s]//g;
640 $d->{cpus
} = $conf->{cpulimit
} // 0;
643 my $res = get_container_disk_usage
($vmid);
644 $d->{disk
} = $res->{used
};
645 $d->{maxdisk
} = $res->{total
};
648 # use 4GB by default ??
649 if (my $rootfs = $conf->{rootfs
}) {
650 my $rootinfo = parse_ct_mountpoint
($rootfs);
651 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
653 $d->{maxdisk
} = 4*1024*1024*1024;
659 $d->{maxmem
} = $conf->{memory
}*1024*1024;
660 $d->{maxswap
} = $conf->{swap
}*1024*1024;
672 foreach my $vmid (keys %$list) {
673 my $d = $list->{$vmid};
674 next if $d->{status
} ne 'running';
676 $d->{uptime
} = 100; # fixme:
678 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
679 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
681 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
682 my @bytes = split(/\n/, $blkio_bytes);
683 foreach my $byte (@bytes) {
684 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
685 $d->{diskread
} = $2 if $key eq 'Read';
686 $d->{diskwrite
} = $2 if $key eq 'Write';
694 my $parse_size = sub {
697 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
698 my ($size, $unit) = ($1, $3);
701 $size = $size * 1024;
702 } elsif ($unit eq 'M') {
703 $size = $size * 1024 * 1024;
704 } elsif ($unit eq 'G') {
705 $size = $size * 1024 * 1024 * 1024;
711 sub parse_ct_mountpoint
{
718 foreach my $p (split (/,/, $data)) {
719 next if $p =~ m/^\s*$/;
721 if ($p =~ m/^(volume|backup|size)=(.+)$/) {
722 my ($k, $v) = ($1, $2);
723 return undef if defined($res->{$k});
726 if (!$res->{volume
} && $p !~ m/=/) {
734 return undef if !$res->{volume
};
736 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
739 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
745 sub print_lxc_network
{
748 die "no network name defined\n" if !$net->{name
};
750 my $res = "name=$net->{name}";
752 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
753 next if !defined($net->{$k});
754 $res .= ",$k=$net->{$k}";
760 sub parse_lxc_network
{
765 return $res if !$data;
767 foreach my $pv (split (/,/, $data)) {
768 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
775 $res->{type
} = 'veth';
776 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
781 sub read_cgroup_value
{
782 my ($group, $vmid, $name, $full) = @_;
784 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
786 return PVE
::Tools
::file_get_contents
($path) if $full;
788 return PVE
::Tools
::file_read_firstline
($path);
791 sub write_cgroup_value
{
792 my ($group, $vmid, $name, $value) = @_;
794 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
795 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
799 sub find_lxc_console_pids
{
803 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
806 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
809 my @args = split(/\0/, $cmdline);
811 # serach for lxc-console -n <vmid>
812 return if scalar(@args) != 3;
813 return if $args[1] ne '-n';
814 return if $args[2] !~ m/^\d+$/;
815 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
819 push @{$res->{$vmid}}, $pid;
831 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
833 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
835 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
840 my $ipv4_reverse_mask = [
876 # Note: we cannot use Net:IP, because that only allows strict
878 sub parse_ipv4_cidr
{
879 my ($cidr, $noerr) = @_;
881 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
882 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
885 return undef if $noerr;
887 die "unable to parse ipv4 address/mask\n";
893 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
896 sub update_lxc_config
{
897 my ($storage_cfg, $vmid, $conf) = @_;
901 die "missing 'arch' - internal error" if !$conf->{arch
};
902 $raw .= "lxc.arch = $conf->{arch}\n";
904 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
905 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
906 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
911 my $ttycount = $conf->{tty
} // 4;
912 $raw .= "lxc.tty = $ttycount\n";
914 my $utsname = $conf->{hostname
} || "CT$vmid";
915 $raw .= "lxc.utsname = $utsname\n";
917 my $memory = $conf->{memory
} || 512;
918 my $swap = $conf->{swap
} // 0;
920 my $lxcmem = int($memory*1024*1024);
921 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
923 my $lxcswap = int(($memory + $swap)*1024*1024);
924 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
926 if (my $cpulimit = $conf->{cpulimit
}) {
927 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
928 my $value = int(100000*$cpulimit);
929 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
932 my $shares = $conf->{cpuunits
} || 1024;
933 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
935 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
936 my $volid = $rootinfo->{volume
};
937 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
939 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
940 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
941 my $rootfs = PVE
::Storage
::path
($storage_cfg, $volid);
942 $raw .= "lxc.rootfs = loop:$rootfs\n";
943 } elsif ($scfg->{type
} eq 'zfspool') {
944 my $rootfs = PVE
::Storage
::path
($storage_cfg, $volid);
945 $raw .= "lxc.rootfs = $rootfs\n";
946 } elsif ($scfg->{type
} eq 'drbd') {
947 my $rootdev = PVE
::Storage
::path
($storage_cfg, $volid);
948 $raw .= "lxc.rootfs = $rootdev\n";
950 die "unsupported storage type '$scfg->{type}'\n";
954 foreach my $k (keys %$conf) {
955 next if $k !~ m/^net(\d+)$/;
957 my $d = parse_lxc_network
($conf->{$k});
959 $raw .= "lxc.network.type = veth\n";
960 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
961 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
962 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
963 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
966 if (my $lxcconf = $conf->{lxc
}) {
967 foreach my $entry (@$lxcconf) {
968 my ($k, $v) = @$entry;
969 $netcount++ if $k eq 'lxc.network.type';
974 $raw .= "lxc.network.type = empty\n" if !$netcount;
976 my $dir = "/var/lib/lxc/$vmid";
977 File
::Path
::mkpath
("$dir/rootfs");
979 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
982 # verify and cleanup nameserver list (replace \0 with ' ')
983 sub verify_nameserver_list
{
984 my ($nameserver_list) = @_;
987 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
988 PVE
::JSONSchema
::pve_verify_ip
($server);
992 return join(' ', @list);
995 sub verify_searchdomain_list
{
996 my ($searchdomain_list) = @_;
999 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1000 # todo: should we add checks for valid dns domains?
1001 push @list, $server;
1004 return join(' ', @list);
1007 sub update_pct_config
{
1008 my ($vmid, $conf, $running, $param, $delete) = @_;
1014 my $pid = find_lxc_pid
($vmid);
1015 $rootdir = "/proc/$pid/root";
1018 if (defined($delete)) {
1019 foreach my $opt (@$delete) {
1020 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1021 die "unable to delete required option '$opt'\n";
1022 } elsif ($opt eq 'swap') {
1023 delete $conf->{$opt};
1024 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1025 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1026 delete $conf->{$opt};
1027 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain') {
1028 delete $conf->{$opt};
1029 push @nohotplug, $opt;
1031 } elsif ($opt =~ m/^net(\d)$/) {
1032 delete $conf->{$opt};
1035 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1039 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1043 # There's no separate swap size to configure, there's memory and "total"
1044 # memory (iow. memory+swap). This means we have to change them together.
1045 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1046 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1047 if (defined($wanted_memory) || defined($wanted_swap)) {
1049 $wanted_memory //= ($conf->{memory
} || 512);
1050 $wanted_swap //= ($conf->{swap
} || 0);
1052 my $total = $wanted_memory + $wanted_swap;
1054 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1055 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1057 $conf->{memory
} = $wanted_memory;
1058 $conf->{swap
} = $wanted_swap;
1060 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1063 foreach my $opt (keys %$param) {
1064 my $value = $param->{$opt};
1065 if ($opt eq 'hostname') {
1066 $conf->{$opt} = $value;
1067 } elsif ($opt eq 'onboot') {
1068 $conf->{$opt} = $value ?
1 : 0;
1069 } elsif ($opt eq 'startup') {
1070 $conf->{$opt} = $value;
1071 } elsif ($opt eq 'tty') {
1072 $conf->{$opt} = $value;
1073 push @nohotplug, $opt;
1075 } elsif ($opt eq 'nameserver') {
1076 my $list = verify_nameserver_list
($value);
1077 $conf->{$opt} = $list;
1078 push @nohotplug, $opt;
1080 } elsif ($opt eq 'searchdomain') {
1081 my $list = verify_searchdomain_list
($value);
1082 $conf->{$opt} = $list;
1083 push @nohotplug, $opt;
1085 } elsif ($opt eq 'cpulimit') {
1086 $conf->{$opt} = $value;
1087 push @nohotplug, $opt; # fixme: hotplug
1089 } elsif ($opt eq 'cpuunits') {
1090 $conf->{$opt} = $value;
1091 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1092 } elsif ($opt eq 'description') {
1093 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1094 } elsif ($opt =~ m/^net(\d+)$/) {
1096 my $net = parse_lxc_network
($value);
1098 $conf->{$opt} = print_lxc_network
($net);
1100 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1103 die "implement me: $opt";
1105 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1108 if ($running && scalar(@nohotplug)) {
1109 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1113 sub get_primary_ips
{
1116 # return data from net0
1118 return undef if !defined($conf->{net0
});
1119 my $net = parse_lxc_network
($conf->{net0
});
1121 my $ipv4 = $net->{ip
};
1123 if ($ipv4 =~ /^(dhcp|manual)$/) {
1129 my $ipv6 = $net->{ip6
};
1131 if ($ipv6 =~ /^(dhcp|manual)$/) {
1138 return ($ipv4, $ipv6);
1142 sub destroy_lxc_container
{
1143 my ($storage_cfg, $vmid, $conf) = @_;
1145 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1146 if (defined($rootinfo->{volume
})) {
1147 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1148 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1150 rmdir "/var/lib/lxc/$vmid/rootfs";
1151 unlink "/var/lib/lxc/$vmid/config";
1152 rmdir "/var/lib/lxc/$vmid";
1153 destroy_config
($vmid);
1155 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1156 #PVE::Tools::run_command($cmd);
1159 sub vm_stop_cleanup
{
1160 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1164 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1165 PVE
::Storage
::deactivate_volumes
($storeage_cfg, [$rootinfo->{volume
}]);
1168 warn $@ if $@; # avoid errors - just warn
1171 my $safe_num_ne = sub {
1174 return 0 if !defined($a) && !defined($b);
1175 return 1 if !defined($a);
1176 return 1 if !defined($b);
1181 my $safe_string_ne = sub {
1184 return 0 if !defined($a) && !defined($b);
1185 return 1 if !defined($a);
1186 return 1 if !defined($b);
1192 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1194 if ($newnet->{type
} ne 'veth') {
1195 # for when there are physical interfaces
1196 die "cannot update interface of type $newnet->{type}";
1199 my $veth = "veth${vmid}i${netid}";
1200 my $eth = $newnet->{name
};
1202 if (my $oldnetcfg = $conf->{$opt}) {
1203 my $oldnet = parse_lxc_network
($oldnetcfg);
1205 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1206 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1208 PVE
::Network
::veth_delete
($veth);
1209 delete $conf->{$opt};
1210 PVE
::LXC
::write_config
($vmid, $conf);
1212 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1214 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1215 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1216 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1218 if ($oldnet->{bridge
}) {
1219 PVE
::Network
::tap_unplug
($veth);
1220 foreach (qw(bridge tag firewall)) {
1221 delete $oldnet->{$_};
1223 $conf->{$opt} = print_lxc_network
($oldnet);
1224 PVE
::LXC
::write_config
($vmid, $conf);
1227 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1228 foreach (qw(bridge tag firewall)) {
1229 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1231 $conf->{$opt} = print_lxc_network
($oldnet);
1232 PVE
::LXC
::write_config
($vmid, $conf);
1235 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1238 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1242 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1244 my $veth = "veth${vmid}i${netid}";
1245 my $vethpeer = $veth . "p";
1246 my $eth = $newnet->{name
};
1248 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1249 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1251 # attach peer in container
1252 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1253 PVE
::Tools
::run_command
($cmd);
1255 # link up peer in container
1256 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1257 PVE
::Tools
::run_command
($cmd);
1259 my $done = { type
=> 'veth' };
1260 foreach (qw(bridge tag firewall hwaddr name)) {
1261 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1263 $conf->{$opt} = print_lxc_network
($done);
1265 PVE
::LXC
::write_config
($vmid, $conf);
1268 sub update_ipconfig
{
1269 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1271 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1273 my $optdata = parse_lxc_network
($conf->{$opt});
1277 my $cmdargs = shift;
1278 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1280 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1282 my $change_ip_config = sub {
1283 my ($ipversion) = @_;
1285 my $family_opt = "-$ipversion";
1286 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1287 my $gw= "gw$suffix";
1288 my $ip= "ip$suffix";
1290 my $newip = $newnet->{$ip};
1291 my $newgw = $newnet->{$gw};
1292 my $oldip = $optdata->{$ip};
1294 my $change_ip = &$safe_string_ne($oldip, $newip);
1295 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1297 return if !$change_ip && !$change_gw;
1299 # step 1: add new IP, if this fails we cancel
1300 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1301 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1308 # step 2: replace gateway
1309 # If this fails we delete the added IP and cancel.
1310 # If it succeeds we save the config and delete the old IP, ignoring
1311 # errors. The config is then saved.
1312 # Note: 'ip route replace' can add
1315 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1318 # the route was not replaced, the old IP is still available
1319 # rollback (delete new IP) and cancel
1321 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1322 warn $@ if $@; # no need to die here
1327 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1328 # if the route was not deleted, the guest might have deleted it manually
1334 # from this point on we save the configuration
1335 # step 3: delete old IP ignoring errors
1336 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1337 # We need to enable promote_secondaries, otherwise our newly added
1338 # address will be removed along with the old one.
1341 if ($ipversion == 4) {
1342 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1343 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1344 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1346 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1348 warn $@ if $@; # no need to die here
1350 if ($ipversion == 4) {
1351 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1355 foreach my $property ($ip, $gw) {
1356 if ($newnet->{$property}) {
1357 $optdata->{$property} = $newnet->{$property};
1359 delete $optdata->{$property};
1362 $conf->{$opt} = print_lxc_network
($optdata);
1363 PVE
::LXC
::write_config
($vmid, $conf);
1364 $lxc_setup->setup_network($conf);
1367 &$change_ip_config(4);
1368 &$change_ip_config(6);
1372 # Internal snapshots
1374 # NOTE: Snapshot create/delete involves several non-atomic
1375 # action, and can take a long time.
1376 # So we try to avoid locking the file and use 'lock' variable
1377 # inside the config file instead.
1379 my $snapshot_copy_config = sub {
1380 my ($source, $dest) = @_;
1382 foreach my $k (keys %$source) {
1383 next if $k eq 'snapshots';
1384 next if $k eq 'snapstate';
1385 next if $k eq 'snaptime';
1386 next if $k eq 'vmstate';
1387 next if $k eq 'lock';
1388 next if $k eq 'digest';
1389 next if $k eq 'description';
1391 $dest->{$k} = $source->{$k};
1395 my $snapshot_prepare = sub {
1396 my ($vmid, $snapname, $comment) = @_;
1400 my $updatefn = sub {
1402 my $conf = load_config
($vmid);
1406 $conf->{lock} = 'snapshot';
1408 die "snapshot name '$snapname' already used\n"
1409 if defined($conf->{snapshots
}->{$snapname});
1411 my $storecfg = PVE
::Storage
::config
();
1412 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1414 $snap = $conf->{snapshots
}->{$snapname} = {};
1416 &$snapshot_copy_config($conf, $snap);
1418 $snap->{'snapstate'} = "prepare";
1419 $snap->{'snaptime'} = time();
1420 $snap->{'description'} = $comment if $comment;
1421 $conf->{snapshots
}->{$snapname} = $snap;
1423 PVE
::LXC
::write_config
($vmid, $conf);
1426 lock_container
($vmid, 10, $updatefn);
1431 my $snapshot_commit = sub {
1432 my ($vmid, $snapname) = @_;
1434 my $updatefn = sub {
1436 my $conf = load_config
($vmid);
1438 die "missing snapshot lock\n"
1439 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1441 die "snapshot '$snapname' does not exist\n"
1442 if !defined($conf->{snapshots
}->{$snapname});
1444 die "wrong snapshot state\n"
1445 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1446 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1448 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1449 delete $conf->{lock};
1450 $conf->{parent
} = $snapname;
1452 PVE
::LXC
::write_config
($vmid, $conf);
1455 lock_container
($vmid, 10 ,$updatefn);
1459 my ($feature, $conf, $storecfg, $snapname) = @_;
1461 #Fixme add other drives if necessary.
1464 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1465 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1467 return $err ?
0 : 1;
1470 sub snapshot_create
{
1471 my ($vmid, $snapname, $comment) = @_;
1473 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1475 my $conf = load_config
($vmid);
1477 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1478 my $running = check_running
($vmid);
1481 PVE
::Tools
::run_command
($cmd);
1484 my $storecfg = PVE
::Storage
::config
();
1485 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1486 my $volid = $rootinfo->{volume
};
1488 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1490 PVE
::Tools
::run_command
($cmd);
1493 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1494 &$snapshot_commit($vmid, $snapname);
1497 snapshot_delete
($vmid, $snapname, 1);
1502 sub snapshot_delete
{
1503 my ($vmid, $snapname, $force) = @_;
1509 my $updatefn = sub {
1511 $conf = load_config
($vmid);
1513 $snap = $conf->{snapshots
}->{$snapname};
1517 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1519 $snap->{snapstate
} = 'delete';
1521 PVE
::LXC
::write_config
($vmid, $conf);
1524 lock_container
($vmid, 10, $updatefn);
1526 my $storecfg = PVE
::Storage
::config
();
1528 my $del_snap = sub {
1532 if ($conf->{parent
} eq $snapname) {
1533 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1534 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1536 delete $conf->{parent
};
1540 delete $conf->{snapshots
}->{$snapname};
1542 PVE
::LXC
::write_config
($vmid, $conf);
1545 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1546 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1547 my $volid = $rootinfo->{volume
};
1550 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1554 if(!$err || ($err && $force)) {
1555 lock_container
($vmid, 10, $del_snap);
1557 die "Can't delete snapshot: $vmid $snapname $err\n";
1562 sub snapshot_rollback
{
1563 my ($vmid, $snapname) = @_;
1565 my $storecfg = PVE
::Storage
::config
();
1567 my $conf = load_config
($vmid);
1569 my $snap = $conf->{snapshots
}->{$snapname};
1571 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1573 my $rootfs = $snap->{rootfs
};
1574 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1575 my $volid = $rootinfo->{volume
};
1577 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1579 my $updatefn = sub {
1581 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1582 if $snap->{snapstate
};
1586 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1588 die "unable to rollback vm $vmid: vm is running\n"
1589 if check_running
($vmid);
1591 $conf->{lock} = 'rollback';
1595 # copy snapshot config to current config
1597 my $tmp_conf = $conf;
1598 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1599 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1600 delete $conf->{snaptime
};
1601 delete $conf->{snapname
};
1602 $conf->{parent
} = $snapname;
1604 PVE
::LXC
::write_config
($vmid, $conf);
1607 my $unlockfn = sub {
1608 delete $conf->{lock};
1609 PVE
::LXC
::write_config
($vmid, $conf);
1612 lock_container
($vmid, 10, $updatefn);
1614 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1616 lock_container
($vmid, 5, $unlockfn);