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