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});
725 if (!$res->{volume
} && $p !~ m/=/) {
733 return undef if !$res->{volume
};
735 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
738 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
744 sub print_lxc_network
{
747 die "no network name defined\n" if !$net->{name
};
749 my $res = "name=$net->{name}";
751 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
752 next if !defined($net->{$k});
753 $res .= ",$k=$net->{$k}";
759 sub parse_lxc_network
{
764 return $res if !$data;
766 foreach my $pv (split (/,/, $data)) {
767 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
774 $res->{type
} = 'veth';
775 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
780 sub read_cgroup_value
{
781 my ($group, $vmid, $name, $full) = @_;
783 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
785 return PVE
::Tools
::file_get_contents
($path) if $full;
787 return PVE
::Tools
::file_read_firstline
($path);
790 sub write_cgroup_value
{
791 my ($group, $vmid, $name, $value) = @_;
793 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
794 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
798 sub find_lxc_console_pids
{
802 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
805 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
808 my @args = split(/\0/, $cmdline);
810 # serach for lxc-console -n <vmid>
811 return if scalar(@args) != 3;
812 return if $args[1] ne '-n';
813 return if $args[2] !~ m/^\d+$/;
814 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
818 push @{$res->{$vmid}}, $pid;
830 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
832 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
834 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
839 my $ipv4_reverse_mask = [
875 # Note: we cannot use Net:IP, because that only allows strict
877 sub parse_ipv4_cidr
{
878 my ($cidr, $noerr) = @_;
880 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
881 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
884 return undef if $noerr;
886 die "unable to parse ipv4 address/mask\n";
892 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
895 sub update_lxc_config
{
896 my ($storage_cfg, $vmid, $conf) = @_;
900 die "missing 'arch' - internal error" if !$conf->{arch
};
901 $raw .= "lxc.arch = $conf->{arch}\n";
903 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
904 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
905 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
910 my $ttycount = $conf->{tty
} // 4;
911 $raw .= "lxc.tty = $ttycount\n";
913 my $utsname = $conf->{hostname
} || "CT$vmid";
914 $raw .= "lxc.utsname = $utsname\n";
916 my $memory = $conf->{memory
} || 512;
917 my $swap = $conf->{swap
} // 0;
919 my $lxcmem = int($memory*1024*1024);
920 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
922 my $lxcswap = int(($memory + $swap)*1024*1024);
923 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
925 if (my $cpulimit = $conf->{cpulimit
}) {
926 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
927 my $value = int(100000*$cpulimit);
928 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
931 my $shares = $conf->{cpuunits
} || 1024;
932 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
934 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
935 my $volid = $rootinfo->{volume
};
936 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
938 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
939 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
940 my $rootfs = PVE
::Storage
::path
($storage_cfg, $volid);
941 $raw .= "lxc.rootfs = loop:$rootfs\n";
942 } elsif ($scfg->{type
} eq 'zfspool') {
943 my $rootfs = PVE
::Storage
::path
($storage_cfg, $volid);
944 $raw .= "lxc.rootfs = $rootfs\n";
948 foreach my $k (keys %$conf) {
949 next if $k !~ m/^net(\d+)$/;
951 my $d = parse_lxc_network
($conf->{$k});
953 $raw .= "lxc.network.type = veth\n";
954 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
955 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
956 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
957 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
960 if (my $lxcconf = $conf->{lxc
}) {
961 foreach my $entry (@$lxcconf) {
962 my ($k, $v) = @$entry;
963 $netcount++ if $k eq 'lxc.network.type';
968 $raw .= "lxc.network.type = empty\n" if !$netcount;
970 my $dir = "/var/lib/lxc/$vmid";
971 File
::Path
::mkpath
("$dir/rootfs");
973 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
976 # verify and cleanup nameserver list (replace \0 with ' ')
977 sub verify_nameserver_list
{
978 my ($nameserver_list) = @_;
981 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
982 PVE
::JSONSchema
::pve_verify_ip
($server);
986 return join(' ', @list);
989 sub verify_searchdomain_list
{
990 my ($searchdomain_list) = @_;
993 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
994 # todo: should we add checks for valid dns domains?
998 return join(' ', @list);
1001 sub update_pct_config
{
1002 my ($vmid, $conf, $running, $param, $delete) = @_;
1008 my $pid = find_lxc_pid
($vmid);
1009 $rootdir = "/proc/$pid/root";
1012 if (defined($delete)) {
1013 foreach my $opt (@$delete) {
1014 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1015 die "unable to delete required option '$opt'\n";
1016 } elsif ($opt eq 'swap') {
1017 delete $conf->{$opt};
1018 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1019 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1020 delete $conf->{$opt};
1021 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain') {
1022 delete $conf->{$opt};
1023 push @nohotplug, $opt;
1025 } elsif ($opt =~ m/^net(\d)$/) {
1026 delete $conf->{$opt};
1029 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1033 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1037 # There's no separate swap size to configure, there's memory and "total"
1038 # memory (iow. memory+swap). This means we have to change them together.
1039 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1040 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1041 if (defined($wanted_memory) || defined($wanted_swap)) {
1043 $wanted_memory //= ($conf->{memory
} || 512);
1044 $wanted_swap //= ($conf->{swap
} || 0);
1046 my $total = $wanted_memory + $wanted_swap;
1048 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1049 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1051 $conf->{memory
} = $wanted_memory;
1052 $conf->{swap
} = $wanted_swap;
1054 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1057 foreach my $opt (keys %$param) {
1058 my $value = $param->{$opt};
1059 if ($opt eq 'hostname') {
1060 $conf->{$opt} = $value;
1061 } elsif ($opt eq 'onboot') {
1062 $conf->{$opt} = $value ?
1 : 0;
1063 } elsif ($opt eq 'startup') {
1064 $conf->{$opt} = $value;
1065 } elsif ($opt eq 'tty') {
1066 $conf->{$opt} = $value;
1067 push @nohotplug, $opt;
1069 } elsif ($opt eq 'nameserver') {
1070 my $list = verify_nameserver_list
($value);
1071 $conf->{$opt} = $list;
1072 push @nohotplug, $opt;
1074 } elsif ($opt eq 'searchdomain') {
1075 my $list = verify_searchdomain_list
($value);
1076 $conf->{$opt} = $list;
1077 push @nohotplug, $opt;
1079 } elsif ($opt eq 'cpulimit') {
1080 $conf->{$opt} = $value;
1081 push @nohotplug, $opt; # fixme: hotplug
1083 } elsif ($opt eq 'cpuunits') {
1084 $conf->{$opt} = $value;
1085 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1086 } elsif ($opt eq 'description') {
1087 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1088 } elsif ($opt =~ m/^net(\d+)$/) {
1090 my $net = parse_lxc_network
($value);
1092 $conf->{$opt} = print_lxc_network
($net);
1094 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1097 die "implement me: $opt";
1099 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1102 if ($running && scalar(@nohotplug)) {
1103 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1107 sub get_primary_ips
{
1110 # return data from net0
1112 return undef if !defined($conf->{net0
});
1113 my $net = parse_lxc_network
($conf->{net0
});
1115 my $ipv4 = $net->{ip
};
1117 if ($ipv4 =~ /^(dhcp|manual)$/) {
1123 my $ipv6 = $net->{ip6
};
1125 if ($ipv6 =~ /^(dhcp|manual)$/) {
1132 return ($ipv4, $ipv6);
1135 sub destroy_lxc_container
{
1136 my ($storage_cfg, $vmid, $conf) = @_;
1138 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1139 if (defined($rootinfo->{volume
})) {
1140 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1141 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1143 rmdir "/var/lib/lxc/$vmid/rootfs";
1144 unlink "/var/lib/lxc/$vmid/config";
1145 rmdir "/var/lib/lxc/$vmid";
1146 destroy_config
($vmid);
1148 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1149 #PVE::Tools::run_command($cmd);
1152 my $safe_num_ne = sub {
1155 return 0 if !defined($a) && !defined($b);
1156 return 1 if !defined($a);
1157 return 1 if !defined($b);
1162 my $safe_string_ne = sub {
1165 return 0 if !defined($a) && !defined($b);
1166 return 1 if !defined($a);
1167 return 1 if !defined($b);
1173 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1175 if ($newnet->{type
} ne 'veth') {
1176 # for when there are physical interfaces
1177 die "cannot update interface of type $newnet->{type}";
1180 my $veth = "veth${vmid}i${netid}";
1181 my $eth = $newnet->{name
};
1183 if (my $oldnetcfg = $conf->{$opt}) {
1184 my $oldnet = parse_lxc_network
($oldnetcfg);
1186 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1187 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1189 PVE
::Network
::veth_delete
($veth);
1190 delete $conf->{$opt};
1191 PVE
::LXC
::write_config
($vmid, $conf);
1193 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1195 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1196 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1197 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1199 if ($oldnet->{bridge
}) {
1200 PVE
::Network
::tap_unplug
($veth);
1201 foreach (qw(bridge tag firewall)) {
1202 delete $oldnet->{$_};
1204 $conf->{$opt} = print_lxc_network
($oldnet);
1205 PVE
::LXC
::write_config
($vmid, $conf);
1208 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1209 foreach (qw(bridge tag firewall)) {
1210 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1212 $conf->{$opt} = print_lxc_network
($oldnet);
1213 PVE
::LXC
::write_config
($vmid, $conf);
1216 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1219 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1223 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1225 my $veth = "veth${vmid}i${netid}";
1226 my $vethpeer = $veth . "p";
1227 my $eth = $newnet->{name
};
1229 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1230 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1232 # attach peer in container
1233 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1234 PVE
::Tools
::run_command
($cmd);
1236 # link up peer in container
1237 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1238 PVE
::Tools
::run_command
($cmd);
1240 my $done = { type
=> 'veth' };
1241 foreach (qw(bridge tag firewall hwaddr name)) {
1242 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1244 $conf->{$opt} = print_lxc_network
($done);
1246 PVE
::LXC
::write_config
($vmid, $conf);
1249 sub update_ipconfig
{
1250 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1252 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1254 my $optdata = parse_lxc_network
($conf->{$opt});
1258 my $cmdargs = shift;
1259 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1261 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1263 my $change_ip_config = sub {
1264 my ($ipversion) = @_;
1266 my $family_opt = "-$ipversion";
1267 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1268 my $gw= "gw$suffix";
1269 my $ip= "ip$suffix";
1271 my $newip = $newnet->{$ip};
1272 my $newgw = $newnet->{$gw};
1273 my $oldip = $optdata->{$ip};
1275 my $change_ip = &$safe_string_ne($oldip, $newip);
1276 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1278 return if !$change_ip && !$change_gw;
1280 # step 1: add new IP, if this fails we cancel
1281 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1282 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1289 # step 2: replace gateway
1290 # If this fails we delete the added IP and cancel.
1291 # If it succeeds we save the config and delete the old IP, ignoring
1292 # errors. The config is then saved.
1293 # Note: 'ip route replace' can add
1296 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1299 # the route was not replaced, the old IP is still available
1300 # rollback (delete new IP) and cancel
1302 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1303 warn $@ if $@; # no need to die here
1308 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1309 # if the route was not deleted, the guest might have deleted it manually
1315 # from this point on we save the configuration
1316 # step 3: delete old IP ignoring errors
1317 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1318 # We need to enable promote_secondaries, otherwise our newly added
1319 # address will be removed along with the old one.
1322 if ($ipversion == 4) {
1323 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1324 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1325 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1327 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1329 warn $@ if $@; # no need to die here
1331 if ($ipversion == 4) {
1332 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1336 foreach my $property ($ip, $gw) {
1337 if ($newnet->{$property}) {
1338 $optdata->{$property} = $newnet->{$property};
1340 delete $optdata->{$property};
1343 $conf->{$opt} = print_lxc_network
($optdata);
1344 PVE
::LXC
::write_config
($vmid, $conf);
1345 $lxc_setup->setup_network($conf);
1348 &$change_ip_config(4);
1349 &$change_ip_config(6);
1353 # Internal snapshots
1355 # NOTE: Snapshot create/delete involves several non-atomic
1356 # action, and can take a long time.
1357 # So we try to avoid locking the file and use 'lock' variable
1358 # inside the config file instead.
1360 my $snapshot_copy_config = sub {
1361 my ($source, $dest) = @_;
1363 foreach my $k (keys %$source) {
1364 next if $k eq 'snapshots';
1365 next if $k eq 'snapstate';
1366 next if $k eq 'snaptime';
1367 next if $k eq 'vmstate';
1368 next if $k eq 'lock';
1369 next if $k eq 'digest';
1370 next if $k eq 'description';
1372 $dest->{$k} = $source->{$k};
1376 my $snapshot_prepare = sub {
1377 my ($vmid, $snapname, $comment) = @_;
1381 my $updatefn = sub {
1383 my $conf = load_config
($vmid);
1387 $conf->{lock} = 'snapshot';
1389 die "snapshot name '$snapname' already used\n"
1390 if defined($conf->{snapshots
}->{$snapname});
1392 my $storecfg = PVE
::Storage
::config
();
1393 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1395 $snap = $conf->{snapshots
}->{$snapname} = {};
1397 &$snapshot_copy_config($conf, $snap);
1399 $snap->{'snapstate'} = "prepare";
1400 $snap->{'snaptime'} = time();
1401 $snap->{'description'} = $comment if $comment;
1402 $conf->{snapshots
}->{$snapname} = $snap;
1404 PVE
::LXC
::write_config
($vmid, $conf);
1407 lock_container
($vmid, 10, $updatefn);
1412 my $snapshot_commit = sub {
1413 my ($vmid, $snapname) = @_;
1415 my $updatefn = sub {
1417 my $conf = load_config
($vmid);
1419 die "missing snapshot lock\n"
1420 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1422 die "snapshot '$snapname' does not exist\n"
1423 if !defined($conf->{snapshots
}->{$snapname});
1425 die "wrong snapshot state\n"
1426 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1427 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1429 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1430 delete $conf->{lock};
1431 $conf->{parent
} = $snapname;
1433 PVE
::LXC
::write_config
($vmid, $conf);
1436 lock_container
($vmid, 10 ,$updatefn);
1440 my ($feature, $conf, $storecfg, $snapname) = @_;
1442 #Fixme add other drives if necessary.
1445 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1446 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1448 return $err ?
0 : 1;
1451 sub snapshot_create
{
1452 my ($vmid, $snapname, $comment) = @_;
1454 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1456 my $conf = load_config
($vmid);
1458 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1459 my $running = check_running
($vmid);
1462 PVE
::Tools
::run_command
($cmd);
1465 my $storecfg = PVE
::Storage
::config
();
1466 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1467 my $volid = $rootinfo->{volume
};
1469 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1471 PVE
::Tools
::run_command
($cmd);
1474 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1475 &$snapshot_commit($vmid, $snapname);
1478 snapshot_delete
($vmid, $snapname, 1);
1483 sub snapshot_delete
{
1484 my ($vmid, $snapname, $force) = @_;
1490 my $updatefn = sub {
1492 $conf = load_config
($vmid);
1494 $snap = $conf->{snapshots
}->{$snapname};
1498 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1500 $snap->{snapstate
} = 'delete';
1502 PVE
::LXC
::write_config
($vmid, $conf);
1505 lock_container
($vmid, 10, $updatefn);
1507 my $storecfg = PVE
::Storage
::config
();
1509 my $del_snap = sub {
1513 if ($conf->{parent
} eq $snapname) {
1514 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1515 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1517 delete $conf->{parent
};
1521 delete $conf->{snapshots
}->{$snapname};
1523 PVE
::LXC
::write_config
($vmid, $conf);
1526 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1527 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1528 my $volid = $rootinfo->{volume
};
1531 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1535 if(!$err || ($err && $force)) {
1536 lock_container
($vmid, 10, $del_snap);
1538 die "Can't delete snapshot: $vmid $snapname $err\n";
1543 sub snapshot_rollback
{
1544 my ($vmid, $snapname) = @_;
1546 my $storecfg = PVE
::Storage
::config
();
1548 my $conf = load_config
($vmid);
1550 my $snap = $conf->{snapshots
}->{$snapname};
1552 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1554 my $rootfs = $snap->{rootfs
};
1555 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1556 my $volid = $rootinfo->{volume
};
1558 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1560 my $updatefn = sub {
1562 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1563 if $snap->{snapstate
};
1567 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1569 die "unable to rollback vm $vmid: vm is running\n"
1570 if check_running
($vmid);
1572 $conf->{lock} = 'rollback';
1576 # copy snapshot config to current config
1578 my $tmp_conf = $conf;
1579 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1580 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1581 delete $conf->{snaptime
};
1582 delete $conf->{snapname
};
1583 $conf->{parent
} = $snapname;
1585 PVE
::LXC
::write_config
($vmid, $conf);
1588 my $unlockfn = sub {
1589 delete $conf->{lock};
1590 PVE
::LXC
::write_config
($vmid, $conf);
1593 lock_container
($vmid, 10, $updatefn);
1595 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1597 lock_container
($vmid, 5, $unlockfn);