]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
add foreach_mountpoint
[pve-container.git] / src / PVE / LXC.pm
1 package PVE::LXC;
2
3 use strict;
4 use warnings;
5 use POSIX qw(EINTR);
6
7 use File::Path;
8 use Fcntl ':flock';
9
10 use PVE::Cluster qw(cfs_register_file cfs_read_file);
11 use PVE::Storage;
12 use PVE::SafeSyslog;
13 use PVE::INotify;
14 use PVE::JSONSchema qw(get_standard_option);
15 use PVE::Tools qw($IPV6RE $IPV4RE);
16 use PVE::Network;
17
18 use Data::Dumper;
19
20 my $nodename = PVE::INotify::nodename();
21
22 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
23
24 PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
25 sub verify_lxc_network {
26 my ($value, $noerr) = @_;
27
28 return $value if parse_lxc_network($value);
29
30 return undef if $noerr;
31
32 die "unable to parse network setting\n";
33 }
34
35 PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
36 sub verify_ct_mountpoint {
37 my ($value, $noerr) = @_;
38
39 return $value if parse_ct_mountpoint($value);
40
41 return undef if $noerr;
42
43 die "unable to parse CT mountpoint options\n";
44 }
45
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.",
50 optional => 1,
51 });
52
53 my $confdesc = {
54 lock => {
55 optional => 1,
56 type => 'string',
57 description => "Lock/unlock the VM.",
58 enum => [qw(migrate backup snapshot rollback)],
59 },
60 onboot => {
61 optional => 1,
62 type => 'boolean',
63 description => "Specifies whether a VM will be started during system bootup.",
64 default => 0,
65 },
66 startup => get_standard_option('pve-startup-order'),
67 template => {
68 optional => 1,
69 type => 'boolean',
70 description => "Enable/disable Template.",
71 default => 0,
72 },
73 arch => {
74 optional => 1,
75 type => 'string',
76 enum => ['amd64', 'i386'],
77 description => "OS architecture type.",
78 default => 'amd64',
79 },
80 ostype => {
81 optional => 1,
82 type => 'string',
83 enum => ['debian', 'ubuntu', 'centos'],
84 description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
85 },
86 console => {
87 optional => 1,
88 type => 'boolean',
89 description => "Attach a console device (/dev/console) to the container.",
90 default => 1,
91 },
92 tty => {
93 optional => 1,
94 type => 'integer',
95 description => "Specify the number of tty available to the container",
96 minimum => 0,
97 maximum => 6,
98 default => 2,
99 },
100 cpulimit => {
101 optional => 1,
102 type => 'number',
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.",
104 minimum => 0,
105 maximum => 128,
106 default => 0,
107 },
108 cpuunits => {
109 optional => 1,
110 type => 'integer',
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.",
112 minimum => 0,
113 maximum => 500000,
114 default => 1024,
115 },
116 memory => {
117 optional => 1,
118 type => 'integer',
119 description => "Amount of RAM for the VM in MB.",
120 minimum => 16,
121 default => 512,
122 },
123 swap => {
124 optional => 1,
125 type => 'integer',
126 description => "Amount of SWAP for the VM in MB.",
127 minimum => 0,
128 default => 512,
129 },
130 hostname => {
131 optional => 1,
132 description => "Set a host name for the container.",
133 type => 'string',
134 maxLength => 255,
135 },
136 description => {
137 optional => 1,
138 type => 'string',
139 description => "Container description. Only used on the configuration web interface.",
140 },
141 searchdomain => {
142 optional => 1,
143 type => 'string',
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.",
145 },
146 nameserver => {
147 optional => 1,
148 type => 'string',
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.",
150 },
151 rootfs => get_standard_option('pve-ct-rootfs'),
152 parent => {
153 optional => 1,
154 type => 'string', format => 'pve-configid',
155 maxLength => 40,
156 description => "Parent snapshot name. This is used internally, and should not be modified.",
157 },
158 snaptime => {
159 optional => 1,
160 description => "Timestamp for snapshots.",
161 type => 'integer',
162 minimum => 0,
163 },
164 };
165
166 my $valid_lxc_conf_keys = {
167 'lxc.include' => 1,
168 'lxc.arch' => 1,
169 'lxc.utsname' => 1,
170 'lxc.haltsignal' => 1,
171 'lxc.rebootsignal' => 1,
172 'lxc.stopsignal' => 1,
173 'lxc.init_cmd' => 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,
186 'lxc.pts' => 1,
187 'lxc.console.logfile' => 1,
188 'lxc.console' => 1,
189 'lxc.tty' => 1,
190 'lxc.devttydir' => 1,
191 'lxc.hook.autodev' => 1,
192 'lxc.autodev' => 1,
193 'lxc.kmsg' => 1,
194 'lxc.mount' => 1,
195 'lxc.mount.entry' => 1,
196 'lxc.mount.auto' => 1,
197 'lxc.rootfs' => 1,
198 'lxc.rootfs.mount' => 1,
199 'lxc.rootfs.options' => 1,
200 # lxc.cgroup.*
201 'lxc.cap.drop' => 1,
202 'lxc.cap.keep' => 1,
203 'lxc.aa_profile' => 1,
204 'lxc.aa_allow_incomplete' => 1,
205 'lxc.se_context' => 1,
206 'lxc.seccomp' => 1,
207 'lxc.id_map' => 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,
215 'lxc.loglevel' => 1,
216 'lxc.logfile' => 1,
217 'lxc.start.auto' => 1,
218 'lxc.start.delay' => 1,
219 'lxc.start.order' => 1,
220 'lxc.group' => 1,
221 'lxc.environment' => 1,
222 'lxc.' => 1,
223 'lxc.' => 1,
224 'lxc.' => 1,
225 'lxc.' => 1,
226 };
227
228 my $MAX_LXC_NETWORKS = 10;
229 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
230 $confdesc->{"net$i"} = {
231 optional => 1,
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>]",
239 };
240 }
241
242 my $MAX_MOUNT_POINTS = 10;
243 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
244 $confdesc->{"mp$i"} = {
245 optional => 1,
246 type => 'string', format => 'pve-ct-mountpoint',
247 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
248 description => "Use volume as container mount point.",
249 optional => 1,
250 };
251 }
252
253 sub write_pct_config {
254 my ($filename, $conf) = @_;
255
256 delete $conf->{snapstate}; # just to be sure
257
258 my $generate_raw_config = sub {
259 my ($conf) = @_;
260
261 my $raw = '';
262
263 # add description as comment to top of file
264 my $descr = $conf->{description} || '';
265 foreach my $cl (split(/\n/, $descr)) {
266 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
267 }
268
269 foreach my $key (sort keys %$conf) {
270 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
271 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
272 $raw .= "$key: $conf->{$key}\n";
273 }
274
275 if (my $lxcconf = $conf->{lxc}) {
276 foreach my $entry (@$lxcconf) {
277 my ($k, $v) = @$entry;
278 $raw .= "$k: $v\n";
279 }
280 }
281
282 return $raw;
283 };
284
285 my $raw = &$generate_raw_config($conf);
286
287 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
288 $raw .= "\n[$snapname]\n";
289 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
290 }
291
292 return $raw;
293 }
294
295 sub check_type {
296 my ($key, $value) = @_;
297
298 die "unknown setting '$key'\n" if !$confdesc->{$key};
299
300 my $type = $confdesc->{$key}->{type};
301
302 if (!defined($value)) {
303 die "got undefined value\n";
304 }
305
306 if ($value =~ m/[\n\r]/) {
307 die "property contains a line feed\n";
308 }
309
310 if ($type eq 'boolean') {
311 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
312 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
313 die "type check ('boolean') failed - got '$value'\n";
314 } elsif ($type eq 'integer') {
315 return int($1) if $value =~ m/^(\d+)$/;
316 die "type check ('integer') failed - got '$value'\n";
317 } elsif ($type eq 'number') {
318 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
319 die "type check ('number') failed - got '$value'\n";
320 } elsif ($type eq 'string') {
321 if (my $fmt = $confdesc->{$key}->{format}) {
322 PVE::JSONSchema::check_format($fmt, $value);
323 return $value;
324 }
325 return $value;
326 } else {
327 die "internal error"
328 }
329 }
330
331 sub parse_pct_config {
332 my ($filename, $raw) = @_;
333
334 return undef if !defined($raw);
335
336 my $res = {
337 digest => Digest::SHA::sha1_hex($raw),
338 snapshots => {},
339 };
340
341 $filename =~ m|/lxc/(\d+).conf$|
342 || die "got strange filename '$filename'";
343
344 my $vmid = $1;
345
346 my $conf = $res;
347 my $descr = '';
348 my $section = '';
349
350 my @lines = split(/\n/, $raw);
351 foreach my $line (@lines) {
352 next if $line =~ m/^\s*$/;
353
354 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
355 $section = $1;
356 $conf->{description} = $descr if $descr;
357 $descr = '';
358 $conf = $res->{snapshots}->{$section} = {};
359 next;
360 }
361
362 if ($line =~ m/^\#(.*)\s*$/) {
363 $descr .= PVE::Tools::decode_text($1) . "\n";
364 next;
365 }
366
367 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
368 my $key = $1;
369 my $value = $3;
370 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
371 push @{$conf->{lxc}}, [$key, $value];
372 } else {
373 warn "vm $vmid - unable to parse config: $line\n";
374 }
375 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
376 $descr .= PVE::Tools::decode_text($2);
377 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
378 $conf->{snapstate} = $1;
379 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
380 my $key = $1;
381 my $value = $2;
382 eval { $value = check_type($key, $value); };
383 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
384 $conf->{$key} = $value;
385 } else {
386 warn "vm $vmid - unable to parse config: $line\n";
387 }
388 }
389
390 $conf->{description} = $descr if $descr;
391
392 delete $res->{snapstate}; # just to be sure
393
394 return $res;
395 }
396
397 sub config_list {
398 my $vmlist = PVE::Cluster::get_vmlist();
399 my $res = {};
400 return $res if !$vmlist || !$vmlist->{ids};
401 my $ids = $vmlist->{ids};
402
403 foreach my $vmid (keys %$ids) {
404 next if !$vmid; # skip CT0
405 my $d = $ids->{$vmid};
406 next if !$d->{node} || $d->{node} ne $nodename;
407 next if !$d->{type} || $d->{type} ne 'lxc';
408 $res->{$vmid}->{type} = 'lxc';
409 }
410 return $res;
411 }
412
413 sub cfs_config_path {
414 my ($vmid, $node) = @_;
415
416 $node = $nodename if !$node;
417 return "nodes/$node/lxc/$vmid.conf";
418 }
419
420 sub config_file {
421 my ($vmid, $node) = @_;
422
423 my $cfspath = cfs_config_path($vmid, $node);
424 return "/etc/pve/$cfspath";
425 }
426
427 sub load_config {
428 my ($vmid) = @_;
429
430 my $cfspath = cfs_config_path($vmid);
431
432 my $conf = PVE::Cluster::cfs_read_file($cfspath);
433 die "container $vmid does not exists\n" if !defined($conf);
434
435 return $conf;
436 }
437
438 sub create_config {
439 my ($vmid, $conf) = @_;
440
441 my $dir = "/etc/pve/nodes/$nodename/lxc";
442 mkdir $dir;
443
444 write_config($vmid, $conf);
445 }
446
447 sub destroy_config {
448 my ($vmid) = @_;
449
450 unlink config_file($vmid, $nodename);
451 }
452
453 sub write_config {
454 my ($vmid, $conf) = @_;
455
456 my $cfspath = cfs_config_path($vmid);
457
458 PVE::Cluster::cfs_write_file($cfspath, $conf);
459 }
460
461 # flock: we use one file handle per process, so lock file
462 # can be called multiple times and succeeds for the same process.
463
464 my $lock_handles = {};
465 my $lockdir = "/run/lock/lxc";
466
467 sub lock_filename {
468 my ($vmid) = @_;
469
470 return "$lockdir/pve-config-{$vmid}.lock";
471 }
472
473 sub lock_aquire {
474 my ($vmid, $timeout) = @_;
475
476 $timeout = 10 if !$timeout;
477 my $mode = LOCK_EX;
478
479 my $filename = lock_filename($vmid);
480
481 mkdir $lockdir if !-d $lockdir;
482
483 my $lock_func = sub {
484 if (!$lock_handles->{$$}->{$filename}) {
485 my $fh = new IO::File(">>$filename") ||
486 die "can't open file - $!\n";
487 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
488 }
489
490 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
491 print STDERR "trying to aquire lock...";
492 my $success;
493 while(1) {
494 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
495 # try again on EINTR (see bug #273)
496 if ($success || ($! != EINTR)) {
497 last;
498 }
499 }
500 if (!$success) {
501 print STDERR " failed\n";
502 die "can't aquire lock - $!\n";
503 }
504
505 $lock_handles->{$$}->{$filename}->{refcount}++;
506
507 print STDERR " OK\n";
508 }
509 };
510
511 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
512 my $err = $@;
513 if ($err) {
514 die "can't lock file '$filename' - $err";
515 }
516 }
517
518 sub lock_release {
519 my ($vmid) = @_;
520
521 my $filename = lock_filename($vmid);
522
523 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
524 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
525 if ($refcount <= 0) {
526 $lock_handles->{$$}->{$filename} = undef;
527 close ($fh);
528 }
529 }
530 }
531
532 sub lock_container {
533 my ($vmid, $timeout, $code, @param) = @_;
534
535 my $res;
536
537 lock_aquire($vmid, $timeout);
538 eval { $res = &$code(@param) };
539 my $err = $@;
540 lock_release($vmid);
541
542 die $err if $err;
543
544 return $res;
545 }
546
547 sub option_exists {
548 my ($name) = @_;
549
550 return defined($confdesc->{$name});
551 }
552
553 # add JSON properties for create and set function
554 sub json_config_properties {
555 my $prop = shift;
556
557 foreach my $opt (keys %$confdesc) {
558 next if $opt eq 'parent' || $opt eq 'snaptime';
559 next if $prop->{$opt};
560 $prop->{$opt} = $confdesc->{$opt};
561 }
562
563 return $prop;
564 }
565
566 sub json_config_properties_no_rootfs {
567 my $prop = shift;
568
569 foreach my $opt (keys %$confdesc) {
570 next if $prop->{$opt};
571 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
572 $prop->{$opt} = $confdesc->{$opt};
573 }
574
575 return $prop;
576 }
577
578 # container status helpers
579
580 sub list_active_containers {
581
582 my $filename = "/proc/net/unix";
583
584 # similar test is used by lcxcontainers.c: list_active_containers
585 my $res = {};
586
587 my $fh = IO::File->new ($filename, "r");
588 return $res if !$fh;
589
590 while (defined(my $line = <$fh>)) {
591 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
592 my $path = $1;
593 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
594 $res->{$1} = 1;
595 }
596 }
597 }
598
599 close($fh);
600
601 return $res;
602 }
603
604 # warning: this is slow
605 sub check_running {
606 my ($vmid) = @_;
607
608 my $active_hash = list_active_containers();
609
610 return 1 if defined($active_hash->{$vmid});
611
612 return undef;
613 }
614
615 sub get_container_disk_usage {
616 my ($vmid) = @_;
617
618 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
619
620 my $res = {
621 total => 0,
622 used => 0,
623 avail => 0,
624 };
625
626 my $parser = sub {
627 my $line = shift;
628 if (my ($fsid, $total, $used, $avail) = $line =~
629 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
630 $res = {
631 total => $total,
632 used => $used,
633 avail => $avail,
634 };
635 }
636 };
637 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
638 warn $@ if $@;
639
640 return $res;
641 }
642
643 sub vmstatus {
644 my ($opt_vmid) = @_;
645
646 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
647
648 my $active_hash = list_active_containers();
649
650 foreach my $vmid (keys %$list) {
651 my $d = $list->{$vmid};
652
653 my $running = defined($active_hash->{$vmid});
654
655 $d->{status} = $running ? 'running' : 'stopped';
656
657 my $cfspath = cfs_config_path($vmid);
658 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
659
660 $d->{name} = $conf->{'hostname'} || "CT$vmid";
661 $d->{name} =~ s/[\s]//g;
662
663 $d->{cpus} = $conf->{cpulimit} // 0;
664
665 if ($running) {
666 my $res = get_container_disk_usage($vmid);
667 $d->{disk} = $res->{used};
668 $d->{maxdisk} = $res->{total};
669 } else {
670 $d->{disk} = 0;
671 # use 4GB by default ??
672 if (my $rootfs = $conf->{rootfs}) {
673 my $rootinfo = parse_ct_mountpoint($rootfs);
674 $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
675 } else {
676 $d->{maxdisk} = 4*1024*1024*1024;
677 }
678 }
679
680 $d->{mem} = 0;
681 $d->{swap} = 0;
682 $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
683 $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
684
685 $d->{uptime} = 0;
686 $d->{cpu} = 0;
687
688 $d->{netout} = 0;
689 $d->{netin} = 0;
690
691 $d->{diskread} = 0;
692 $d->{diskwrite} = 0;
693
694 $d->{template} = is_template($conf);
695 }
696
697 foreach my $vmid (keys %$list) {
698 my $d = $list->{$vmid};
699 next if $d->{status} ne 'running';
700
701 $d->{uptime} = 100; # fixme:
702
703 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
704 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
705
706 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
707 my @bytes = split(/\n/, $blkio_bytes);
708 foreach my $byte (@bytes) {
709 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
710 $d->{diskread} = $2 if $key eq 'Read';
711 $d->{diskwrite} = $2 if $key eq 'Write';
712 }
713 }
714 }
715
716 return $list;
717 }
718
719 my $parse_size = sub {
720 my ($value) = @_;
721
722 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
723 my ($size, $unit) = ($1, $3);
724 if ($unit) {
725 if ($unit eq 'K') {
726 $size = $size * 1024;
727 } elsif ($unit eq 'M') {
728 $size = $size * 1024 * 1024;
729 } elsif ($unit eq 'G') {
730 $size = $size * 1024 * 1024 * 1024;
731 }
732 }
733 return int($size);
734 };
735
736 sub parse_ct_mountpoint {
737 my ($data) = @_;
738
739 $data //= '';
740
741 my $res = {};
742
743 foreach my $p (split (/,/, $data)) {
744 next if $p =~ m/^\s*$/;
745
746 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
747 my ($k, $v) = ($1, $2);
748 return undef if defined($res->{$k});
749 $res->{$k} = $v;
750 } else {
751 if (!$res->{volume} && $p !~ m/=/) {
752 $res->{volume} = $p;
753 } else {
754 return undef;
755 }
756 }
757 }
758
759 return undef if !$res->{volume};
760
761 return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
762
763 if ($res->{size}) {
764 return undef if !defined($res->{size} = &$parse_size($res->{size}));
765 }
766
767 return $res;
768 }
769
770 sub print_ct_mountpoint {
771 my ($info) = @_;
772
773 my $opts = '';
774
775 die "missing volume\n" if !$info->{volume};
776
777 foreach my $o ('size', 'backup') {
778 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
779 }
780
781 return "$info->{volume}$opts";
782 }
783
784 sub print_lxc_network {
785 my $net = shift;
786
787 die "no network name defined\n" if !$net->{name};
788
789 my $res = "name=$net->{name}";
790
791 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
792 next if !defined($net->{$k});
793 $res .= ",$k=$net->{$k}";
794 }
795
796 return $res;
797 }
798
799 sub parse_lxc_network {
800 my ($data) = @_;
801
802 my $res = {};
803
804 return $res if !$data;
805
806 foreach my $pv (split (/,/, $data)) {
807 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
808 $res->{$1} = $2;
809 } else {
810 return undef;
811 }
812 }
813
814 $res->{type} = 'veth';
815 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
816
817 return $res;
818 }
819
820 sub read_cgroup_value {
821 my ($group, $vmid, $name, $full) = @_;
822
823 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
824
825 return PVE::Tools::file_get_contents($path) if $full;
826
827 return PVE::Tools::file_read_firstline($path);
828 }
829
830 sub write_cgroup_value {
831 my ($group, $vmid, $name, $value) = @_;
832
833 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
834 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
835
836 }
837
838 sub find_lxc_console_pids {
839
840 my $res = {};
841
842 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
843 my ($pid) = @_;
844
845 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
846 return if !$cmdline;
847
848 my @args = split(/\0/, $cmdline);
849
850 # serach for lxc-console -n <vmid>
851 return if scalar(@args) != 3;
852 return if $args[1] ne '-n';
853 return if $args[2] !~ m/^\d+$/;
854 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
855
856 my $vmid = $args[2];
857
858 push @{$res->{$vmid}}, $pid;
859 });
860
861 return $res;
862 }
863
864 sub find_lxc_pid {
865 my ($vmid) = @_;
866
867 my $pid = undef;
868 my $parser = sub {
869 my $line = shift;
870 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
871 };
872 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
873
874 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
875
876 return $pid;
877 }
878
879 my $ipv4_reverse_mask = [
880 '0.0.0.0',
881 '128.0.0.0',
882 '192.0.0.0',
883 '224.0.0.0',
884 '240.0.0.0',
885 '248.0.0.0',
886 '252.0.0.0',
887 '254.0.0.0',
888 '255.0.0.0',
889 '255.128.0.0',
890 '255.192.0.0',
891 '255.224.0.0',
892 '255.240.0.0',
893 '255.248.0.0',
894 '255.252.0.0',
895 '255.254.0.0',
896 '255.255.0.0',
897 '255.255.128.0',
898 '255.255.192.0',
899 '255.255.224.0',
900 '255.255.240.0',
901 '255.255.248.0',
902 '255.255.252.0',
903 '255.255.254.0',
904 '255.255.255.0',
905 '255.255.255.128',
906 '255.255.255.192',
907 '255.255.255.224',
908 '255.255.255.240',
909 '255.255.255.248',
910 '255.255.255.252',
911 '255.255.255.254',
912 '255.255.255.255',
913 ];
914
915 # Note: we cannot use Net:IP, because that only allows strict
916 # CIDR networks
917 sub parse_ipv4_cidr {
918 my ($cidr, $noerr) = @_;
919
920 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
921 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
922 }
923
924 return undef if $noerr;
925
926 die "unable to parse ipv4 address/mask\n";
927 }
928
929 sub check_lock {
930 my ($conf) = @_;
931
932 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
933 }
934
935 sub update_lxc_config {
936 my ($storage_cfg, $vmid, $conf) = @_;
937
938 my $dir = "/var/lib/lxc/$vmid";
939
940 if ($conf->{template}) {
941
942 unlink "$dir/config";
943
944 return;
945 }
946
947 my $raw = '';
948
949 die "missing 'arch' - internal error" if !$conf->{arch};
950 $raw .= "lxc.arch = $conf->{arch}\n";
951
952 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
953 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
954 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
955 } else {
956 die "implement me";
957 }
958
959 if (!has_dev_console($conf)) {
960 $raw .= "lxc.console = none\n";
961 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
962 }
963
964 my $ttycount = get_tty_count($conf);
965 $raw .= "lxc.tty = $ttycount\n";
966
967 my $utsname = $conf->{hostname} || "CT$vmid";
968 $raw .= "lxc.utsname = $utsname\n";
969
970 my $memory = $conf->{memory} || 512;
971 my $swap = $conf->{swap} // 0;
972
973 my $lxcmem = int($memory*1024*1024);
974 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
975
976 my $lxcswap = int(($memory + $swap)*1024*1024);
977 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
978
979 if (my $cpulimit = $conf->{cpulimit}) {
980 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
981 my $value = int(100000*$cpulimit);
982 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
983 }
984
985 my $shares = $conf->{cpuunits} || 1024;
986 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
987
988 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
989 my $volid = $rootinfo->{volume};
990 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
991
992 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
993 PVE::Storage::parse_volname($storage_cfg, $volid);
994
995 die "unable to use template as rootfs\n" if $isBase;
996
997 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
998 my $path = PVE::Storage::path($storage_cfg, $volid);
999
1000 if ($format eq 'subvol') {
1001 $raw .= "lxc.rootfs = $path\n";
1002 } elsif ($format eq 'raw') {
1003 if ($scfg->{path}) {
1004 $raw .= "lxc.rootfs = loop:$path\n";
1005 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1006 $raw .= "lxc.rootfs = $path\n";
1007 } else {
1008 die "unsupported storage type '$scfg->{type}'\n";
1009 }
1010 } else {
1011 die "unsupported image format '$format'\n";
1012 }
1013
1014 my $netcount = 0;
1015 foreach my $k (keys %$conf) {
1016 next if $k !~ m/^net(\d+)$/;
1017 my $ind = $1;
1018 my $d = parse_lxc_network($conf->{$k});
1019 $netcount++;
1020 $raw .= "lxc.network.type = veth\n";
1021 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1022 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1023 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1024 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
1025 }
1026
1027 if (my $lxcconf = $conf->{lxc}) {
1028 foreach my $entry (@$lxcconf) {
1029 my ($k, $v) = @$entry;
1030 $netcount++ if $k eq 'lxc.network.type';
1031 $raw .= "$k = $v\n";
1032 }
1033 }
1034
1035 $raw .= "lxc.network.type = empty\n" if !$netcount;
1036
1037 File::Path::mkpath("$dir/rootfs");
1038
1039 PVE::Tools::file_set_contents("$dir/config", $raw);
1040 }
1041
1042 # verify and cleanup nameserver list (replace \0 with ' ')
1043 sub verify_nameserver_list {
1044 my ($nameserver_list) = @_;
1045
1046 my @list = ();
1047 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1048 PVE::JSONSchema::pve_verify_ip($server);
1049 push @list, $server;
1050 }
1051
1052 return join(' ', @list);
1053 }
1054
1055 sub verify_searchdomain_list {
1056 my ($searchdomain_list) = @_;
1057
1058 my @list = ();
1059 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1060 # todo: should we add checks for valid dns domains?
1061 push @list, $server;
1062 }
1063
1064 return join(' ', @list);
1065 }
1066
1067 sub update_pct_config {
1068 my ($vmid, $conf, $running, $param, $delete) = @_;
1069
1070 my @nohotplug;
1071
1072 my $rootdir;
1073 if ($running) {
1074 my $pid = find_lxc_pid($vmid);
1075 $rootdir = "/proc/$pid/root";
1076 }
1077
1078 if (defined($delete)) {
1079 foreach my $opt (@$delete) {
1080 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1081 die "unable to delete required option '$opt'\n";
1082 } elsif ($opt eq 'swap') {
1083 delete $conf->{$opt};
1084 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1085 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1086 delete $conf->{$opt};
1087 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1088 $opt eq 'tty' || $opt eq 'console') {
1089 delete $conf->{$opt};
1090 push @nohotplug, $opt;
1091 next if $running;
1092 } elsif ($opt =~ m/^net(\d)$/) {
1093 delete $conf->{$opt};
1094 next if !$running;
1095 my $netid = $1;
1096 PVE::Network::veth_delete("veth${vmid}i$netid");
1097 } else {
1098 die "implement me"
1099 }
1100 PVE::LXC::write_config($vmid, $conf) if $running;
1101 }
1102 }
1103
1104 # There's no separate swap size to configure, there's memory and "total"
1105 # memory (iow. memory+swap). This means we have to change them together.
1106 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1107 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
1108 if (defined($wanted_memory) || defined($wanted_swap)) {
1109
1110 $wanted_memory //= ($conf->{memory} || 512);
1111 $wanted_swap //= ($conf->{swap} || 0);
1112
1113 my $total = $wanted_memory + $wanted_swap;
1114 if ($running) {
1115 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1116 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1117 }
1118 $conf->{memory} = $wanted_memory;
1119 $conf->{swap} = $wanted_swap;
1120
1121 PVE::LXC::write_config($vmid, $conf) if $running;
1122 }
1123
1124 foreach my $opt (keys %$param) {
1125 my $value = $param->{$opt};
1126 if ($opt eq 'hostname') {
1127 $conf->{$opt} = $value;
1128 } elsif ($opt eq 'onboot') {
1129 $conf->{$opt} = $value ? 1 : 0;
1130 } elsif ($opt eq 'startup') {
1131 $conf->{$opt} = $value;
1132 } elsif ($opt eq 'tty' || $opt eq 'console') {
1133 $conf->{$opt} = $value;
1134 push @nohotplug, $opt;
1135 next if $running;
1136 } elsif ($opt eq 'nameserver') {
1137 my $list = verify_nameserver_list($value);
1138 $conf->{$opt} = $list;
1139 push @nohotplug, $opt;
1140 next if $running;
1141 } elsif ($opt eq 'searchdomain') {
1142 my $list = verify_searchdomain_list($value);
1143 $conf->{$opt} = $list;
1144 push @nohotplug, $opt;
1145 next if $running;
1146 } elsif ($opt eq 'cpulimit') {
1147 $conf->{$opt} = $value;
1148 push @nohotplug, $opt; # fixme: hotplug
1149 next;
1150 } elsif ($opt eq 'cpuunits') {
1151 $conf->{$opt} = $value;
1152 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1153 } elsif ($opt eq 'description') {
1154 $conf->{$opt} = PVE::Tools::encode_text($value);
1155 } elsif ($opt =~ m/^net(\d+)$/) {
1156 my $netid = $1;
1157 my $net = parse_lxc_network($value);
1158 if (!$running) {
1159 $conf->{$opt} = print_lxc_network($net);
1160 } else {
1161 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1162 }
1163 } else {
1164 die "implement me: $opt";
1165 }
1166 PVE::LXC::write_config($vmid, $conf) if $running;
1167 }
1168
1169 if ($running && scalar(@nohotplug)) {
1170 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1171 }
1172 }
1173
1174 sub has_dev_console {
1175 my ($conf) = @_;
1176
1177 return !(defined($conf->{console}) && !$conf->{console});
1178 }
1179
1180 sub get_tty_count {
1181 my ($conf) = @_;
1182
1183 return $conf->{tty} // $confdesc->{tty}->{default};
1184 }
1185
1186 sub get_primary_ips {
1187 my ($conf) = @_;
1188
1189 # return data from net0
1190
1191 return undef if !defined($conf->{net0});
1192 my $net = parse_lxc_network($conf->{net0});
1193
1194 my $ipv4 = $net->{ip};
1195 if ($ipv4) {
1196 if ($ipv4 =~ /^(dhcp|manual)$/) {
1197 $ipv4 = undef
1198 } else {
1199 $ipv4 =~ s!/\d+$!!;
1200 }
1201 }
1202 my $ipv6 = $net->{ip6};
1203 if ($ipv6) {
1204 if ($ipv6 =~ /^(dhcp|manual)$/) {
1205 $ipv6 = undef;
1206 } else {
1207 $ipv6 =~ s!/\d+$!!;
1208 }
1209 }
1210
1211 return ($ipv4, $ipv6);
1212 }
1213
1214
1215 sub destroy_lxc_container {
1216 my ($storage_cfg, $vmid, $conf) = @_;
1217
1218 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1219 if (defined($rootinfo->{volume})) {
1220 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
1221 PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
1222 }
1223 rmdir "/var/lib/lxc/$vmid/rootfs";
1224 unlink "/var/lib/lxc/$vmid/config";
1225 rmdir "/var/lib/lxc/$vmid";
1226 destroy_config($vmid);
1227
1228 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1229 #PVE::Tools::run_command($cmd);
1230 }
1231
1232 sub vm_stop_cleanup {
1233 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1234
1235 eval {
1236 if (!$keepActive) {
1237 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1238 PVE::Storage::deactivate_volumes($storeage_cfg, [$rootinfo->{volume}]);
1239 }
1240 };
1241 warn $@ if $@; # avoid errors - just warn
1242 }
1243
1244 my $safe_num_ne = sub {
1245 my ($a, $b) = @_;
1246
1247 return 0 if !defined($a) && !defined($b);
1248 return 1 if !defined($a);
1249 return 1 if !defined($b);
1250
1251 return $a != $b;
1252 };
1253
1254 my $safe_string_ne = sub {
1255 my ($a, $b) = @_;
1256
1257 return 0 if !defined($a) && !defined($b);
1258 return 1 if !defined($a);
1259 return 1 if !defined($b);
1260
1261 return $a ne $b;
1262 };
1263
1264 sub update_net {
1265 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1266
1267 if ($newnet->{type} ne 'veth') {
1268 # for when there are physical interfaces
1269 die "cannot update interface of type $newnet->{type}";
1270 }
1271
1272 my $veth = "veth${vmid}i${netid}";
1273 my $eth = $newnet->{name};
1274
1275 if (my $oldnetcfg = $conf->{$opt}) {
1276 my $oldnet = parse_lxc_network($oldnetcfg);
1277
1278 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1279 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
1280
1281 PVE::Network::veth_delete($veth);
1282 delete $conf->{$opt};
1283 PVE::LXC::write_config($vmid, $conf);
1284
1285 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1286
1287 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1288 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1289 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
1290
1291 if ($oldnet->{bridge}) {
1292 PVE::Network::tap_unplug($veth);
1293 foreach (qw(bridge tag firewall)) {
1294 delete $oldnet->{$_};
1295 }
1296 $conf->{$opt} = print_lxc_network($oldnet);
1297 PVE::LXC::write_config($vmid, $conf);
1298 }
1299
1300 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1301 foreach (qw(bridge tag firewall)) {
1302 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1303 }
1304 $conf->{$opt} = print_lxc_network($oldnet);
1305 PVE::LXC::write_config($vmid, $conf);
1306 }
1307 } else {
1308 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1309 }
1310
1311 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1312 }
1313
1314 sub hotplug_net {
1315 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1316
1317 my $veth = "veth${vmid}i${netid}";
1318 my $vethpeer = $veth . "p";
1319 my $eth = $newnet->{name};
1320
1321 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1322 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1323
1324 # attach peer in container
1325 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1326 PVE::Tools::run_command($cmd);
1327
1328 # link up peer in container
1329 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1330 PVE::Tools::run_command($cmd);
1331
1332 my $done = { type => 'veth' };
1333 foreach (qw(bridge tag firewall hwaddr name)) {
1334 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1335 }
1336 $conf->{$opt} = print_lxc_network($done);
1337
1338 PVE::LXC::write_config($vmid, $conf);
1339 }
1340
1341 sub update_ipconfig {
1342 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1343
1344 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1345
1346 my $optdata = parse_lxc_network($conf->{$opt});
1347 my $deleted = [];
1348 my $added = [];
1349 my $nscmd = sub {
1350 my $cmdargs = shift;
1351 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1352 };
1353 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1354
1355 my $change_ip_config = sub {
1356 my ($ipversion) = @_;
1357
1358 my $family_opt = "-$ipversion";
1359 my $suffix = $ipversion == 4 ? '' : $ipversion;
1360 my $gw= "gw$suffix";
1361 my $ip= "ip$suffix";
1362
1363 my $newip = $newnet->{$ip};
1364 my $newgw = $newnet->{$gw};
1365 my $oldip = $optdata->{$ip};
1366
1367 my $change_ip = &$safe_string_ne($oldip, $newip);
1368 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1369
1370 return if !$change_ip && !$change_gw;
1371
1372 # step 1: add new IP, if this fails we cancel
1373 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1374 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1375 if (my $err = $@) {
1376 warn $err;
1377 return;
1378 }
1379 }
1380
1381 # step 2: replace gateway
1382 # If this fails we delete the added IP and cancel.
1383 # If it succeeds we save the config and delete the old IP, ignoring
1384 # errors. The config is then saved.
1385 # Note: 'ip route replace' can add
1386 if ($change_gw) {
1387 if ($newgw) {
1388 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1389 if (my $err = $@) {
1390 warn $err;
1391 # the route was not replaced, the old IP is still available
1392 # rollback (delete new IP) and cancel
1393 if ($change_ip) {
1394 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1395 warn $@ if $@; # no need to die here
1396 }
1397 return;
1398 }
1399 } else {
1400 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1401 # if the route was not deleted, the guest might have deleted it manually
1402 # warn and continue
1403 warn $@ if $@;
1404 }
1405 }
1406
1407 # from this point on we save the configuration
1408 # step 3: delete old IP ignoring errors
1409 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1410 # We need to enable promote_secondaries, otherwise our newly added
1411 # address will be removed along with the old one.
1412 my $promote = 0;
1413 eval {
1414 if ($ipversion == 4) {
1415 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1416 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1417 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1418 }
1419 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1420 };
1421 warn $@ if $@; # no need to die here
1422
1423 if ($ipversion == 4) {
1424 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1425 }
1426 }
1427
1428 foreach my $property ($ip, $gw) {
1429 if ($newnet->{$property}) {
1430 $optdata->{$property} = $newnet->{$property};
1431 } else {
1432 delete $optdata->{$property};
1433 }
1434 }
1435 $conf->{$opt} = print_lxc_network($optdata);
1436 PVE::LXC::write_config($vmid, $conf);
1437 $lxc_setup->setup_network($conf);
1438 };
1439
1440 &$change_ip_config(4);
1441 &$change_ip_config(6);
1442
1443 }
1444
1445 # Internal snapshots
1446
1447 # NOTE: Snapshot create/delete involves several non-atomic
1448 # action, and can take a long time.
1449 # So we try to avoid locking the file and use 'lock' variable
1450 # inside the config file instead.
1451
1452 my $snapshot_copy_config = sub {
1453 my ($source, $dest) = @_;
1454
1455 foreach my $k (keys %$source) {
1456 next if $k eq 'snapshots';
1457 next if $k eq 'snapstate';
1458 next if $k eq 'snaptime';
1459 next if $k eq 'vmstate';
1460 next if $k eq 'lock';
1461 next if $k eq 'digest';
1462 next if $k eq 'description';
1463
1464 $dest->{$k} = $source->{$k};
1465 }
1466 };
1467
1468 my $snapshot_prepare = sub {
1469 my ($vmid, $snapname, $comment) = @_;
1470
1471 my $snap;
1472
1473 my $updatefn = sub {
1474
1475 my $conf = load_config($vmid);
1476
1477 die "you can't take a snapshot if it's a template\n"
1478 if is_template($conf);
1479
1480 check_lock($conf);
1481
1482 $conf->{lock} = 'snapshot';
1483
1484 die "snapshot name '$snapname' already used\n"
1485 if defined($conf->{snapshots}->{$snapname});
1486
1487 my $storecfg = PVE::Storage::config();
1488 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1489
1490 $snap = $conf->{snapshots}->{$snapname} = {};
1491
1492 &$snapshot_copy_config($conf, $snap);
1493
1494 $snap->{'snapstate'} = "prepare";
1495 $snap->{'snaptime'} = time();
1496 $snap->{'description'} = $comment if $comment;
1497 $conf->{snapshots}->{$snapname} = $snap;
1498
1499 PVE::LXC::write_config($vmid, $conf);
1500 };
1501
1502 lock_container($vmid, 10, $updatefn);
1503
1504 return $snap;
1505 };
1506
1507 my $snapshot_commit = sub {
1508 my ($vmid, $snapname) = @_;
1509
1510 my $updatefn = sub {
1511
1512 my $conf = load_config($vmid);
1513
1514 die "missing snapshot lock\n"
1515 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1516
1517 die "snapshot '$snapname' does not exist\n"
1518 if !defined($conf->{snapshots}->{$snapname});
1519
1520 die "wrong snapshot state\n"
1521 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1522 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
1523
1524 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1525 delete $conf->{lock};
1526 $conf->{parent} = $snapname;
1527
1528 PVE::LXC::write_config($vmid, $conf);
1529 };
1530
1531 lock_container($vmid, 10 ,$updatefn);
1532 };
1533
1534 sub has_feature {
1535 my ($feature, $conf, $storecfg, $snapname) = @_;
1536
1537 #Fixme add other drives if necessary.
1538 my $err;
1539
1540 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1541 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
1542
1543 return $err ? 0 : 1;
1544 }
1545
1546 sub snapshot_create {
1547 my ($vmid, $snapname, $comment) = @_;
1548
1549 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1550
1551 my $conf = load_config($vmid);
1552
1553 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1554 my $running = check_running($vmid);
1555 eval {
1556 if ($running) {
1557 PVE::Tools::run_command($cmd);
1558 };
1559
1560 my $storecfg = PVE::Storage::config();
1561 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1562 my $volid = $rootinfo->{volume};
1563
1564 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1565 if ($running) {
1566 PVE::Tools::run_command($cmd);
1567 };
1568
1569 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1570 &$snapshot_commit($vmid, $snapname);
1571 };
1572 if(my $err = $@) {
1573 snapshot_delete($vmid, $snapname, 1);
1574 die "$err\n";
1575 }
1576 }
1577
1578 sub snapshot_delete {
1579 my ($vmid, $snapname, $force) = @_;
1580
1581 my $snap;
1582
1583 my $conf;
1584
1585 my $updatefn = sub {
1586
1587 $conf = load_config($vmid);
1588
1589 die "you can't delete a snapshot if vm is a template\n"
1590 if is_template($conf);
1591
1592 $snap = $conf->{snapshots}->{$snapname};
1593
1594 check_lock($conf);
1595
1596 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1597
1598 $snap->{snapstate} = 'delete';
1599
1600 PVE::LXC::write_config($vmid, $conf);
1601 };
1602
1603 lock_container($vmid, 10, $updatefn);
1604
1605 my $storecfg = PVE::Storage::config();
1606
1607 my $del_snap = sub {
1608
1609 check_lock($conf);
1610
1611 if ($conf->{parent} eq $snapname) {
1612 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1613 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
1614 } else {
1615 delete $conf->{parent};
1616 }
1617 }
1618
1619 delete $conf->{snapshots}->{$snapname};
1620
1621 PVE::LXC::write_config($vmid, $conf);
1622 };
1623
1624 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1625 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1626 my $volid = $rootinfo->{volume};
1627
1628 eval {
1629 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1630 };
1631 my $err = $@;
1632
1633 if(!$err || ($err && $force)) {
1634 lock_container($vmid, 10, $del_snap);
1635 if ($err) {
1636 die "Can't delete snapshot: $vmid $snapname $err\n";
1637 }
1638 }
1639 }
1640
1641 sub snapshot_rollback {
1642 my ($vmid, $snapname) = @_;
1643
1644 my $storecfg = PVE::Storage::config();
1645
1646 my $conf = load_config($vmid);
1647
1648 die "you can't rollback if vm is a template\n" if is_template($conf);
1649
1650 my $snap = $conf->{snapshots}->{$snapname};
1651
1652 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1653
1654 my $rootfs = $snap->{rootfs};
1655 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1656 my $volid = $rootinfo->{volume};
1657
1658 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
1659
1660 my $updatefn = sub {
1661
1662 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1663 if $snap->{snapstate};
1664
1665 check_lock($conf);
1666
1667 system("lxc-stop -n $vmid --kill") if check_running($vmid);
1668
1669 die "unable to rollback vm $vmid: vm is running\n"
1670 if check_running($vmid);
1671
1672 $conf->{lock} = 'rollback';
1673
1674 my $forcemachine;
1675
1676 # copy snapshot config to current config
1677
1678 my $tmp_conf = $conf;
1679 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
1680 $conf->{snapshots} = $tmp_conf->{snapshots};
1681 delete $conf->{snaptime};
1682 delete $conf->{snapname};
1683 $conf->{parent} = $snapname;
1684
1685 PVE::LXC::write_config($vmid, $conf);
1686 };
1687
1688 my $unlockfn = sub {
1689 delete $conf->{lock};
1690 PVE::LXC::write_config($vmid, $conf);
1691 };
1692
1693 lock_container($vmid, 10, $updatefn);
1694
1695 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
1696
1697 lock_container($vmid, 5, $unlockfn);
1698 }
1699
1700 sub template_create {
1701 my ($vmid, $conf) = @_;
1702
1703 my $storecfg = PVE::Storage::config();
1704
1705 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1706 my $volid = $rootinfo->{volume};
1707
1708 die "Template feature is not available for '$volid'\n"
1709 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1710
1711 PVE::Storage::activate_volumes($storecfg, [$volid]);
1712
1713 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1714 $rootinfo->{volume} = $template_volid;
1715 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
1716
1717 write_config($vmid, $conf);
1718 }
1719
1720 sub is_template {
1721 my ($conf) = @_;
1722
1723 return 1 if defined $conf->{template} && $conf->{template} == 1;
1724 }
1725
1726 sub foreach_mountpoint {
1727 my ($conf, $func) = @_;
1728
1729 foreach my $ms (keys %$conf) {
1730 next if $ms !~ m/^mp(\d+)/ && $ms ne 'rootfs';
1731
1732 my $mountpoint = parse_ct_mountpoint($conf->{$ms});
1733 next if !$mountpoint;
1734
1735 &$func($ms, $mountpoint);
1736 }
1737 }
1738
1739 1;