]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
d6d7af8886bb99f04802a4ee31c20d3a14117269
[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 (!has_dev_console($conf)) {
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 has_dev_console {
1164 my ($conf) = @_;
1165
1166 return !(defined($conf->{console}) && !$conf->{console});
1167 }
1168
1169 sub get_tty_count {
1170 my ($conf) = @_;
1171
1172 return $conf->{tty} // $confdesc->{tty}->{default};
1173 }
1174
1175 sub get_primary_ips {
1176 my ($conf) = @_;
1177
1178 # return data from net0
1179
1180 return undef if !defined($conf->{net0});
1181 my $net = parse_lxc_network($conf->{net0});
1182
1183 my $ipv4 = $net->{ip};
1184 if ($ipv4) {
1185 if ($ipv4 =~ /^(dhcp|manual)$/) {
1186 $ipv4 = undef
1187 } else {
1188 $ipv4 =~ s!/\d+$!!;
1189 }
1190 }
1191 my $ipv6 = $net->{ip6};
1192 if ($ipv6) {
1193 if ($ipv6 =~ /^(dhcp|manual)$/) {
1194 $ipv6 = undef;
1195 } else {
1196 $ipv6 =~ s!/\d+$!!;
1197 }
1198 }
1199
1200 return ($ipv4, $ipv6);
1201 }
1202
1203
1204 sub destroy_lxc_container {
1205 my ($storage_cfg, $vmid, $conf) = @_;
1206
1207 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1208 if (defined($rootinfo->{volume})) {
1209 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
1210 PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
1211 }
1212 rmdir "/var/lib/lxc/$vmid/rootfs";
1213 unlink "/var/lib/lxc/$vmid/config";
1214 rmdir "/var/lib/lxc/$vmid";
1215 destroy_config($vmid);
1216
1217 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1218 #PVE::Tools::run_command($cmd);
1219 }
1220
1221 sub vm_stop_cleanup {
1222 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1223
1224 eval {
1225 if (!$keepActive) {
1226 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1227 PVE::Storage::deactivate_volumes($storeage_cfg, [$rootinfo->{volume}]);
1228 }
1229 };
1230 warn $@ if $@; # avoid errors - just warn
1231 }
1232
1233 my $safe_num_ne = sub {
1234 my ($a, $b) = @_;
1235
1236 return 0 if !defined($a) && !defined($b);
1237 return 1 if !defined($a);
1238 return 1 if !defined($b);
1239
1240 return $a != $b;
1241 };
1242
1243 my $safe_string_ne = sub {
1244 my ($a, $b) = @_;
1245
1246 return 0 if !defined($a) && !defined($b);
1247 return 1 if !defined($a);
1248 return 1 if !defined($b);
1249
1250 return $a ne $b;
1251 };
1252
1253 sub update_net {
1254 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1255
1256 if ($newnet->{type} ne 'veth') {
1257 # for when there are physical interfaces
1258 die "cannot update interface of type $newnet->{type}";
1259 }
1260
1261 my $veth = "veth${vmid}i${netid}";
1262 my $eth = $newnet->{name};
1263
1264 if (my $oldnetcfg = $conf->{$opt}) {
1265 my $oldnet = parse_lxc_network($oldnetcfg);
1266
1267 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1268 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
1269
1270 PVE::Network::veth_delete($veth);
1271 delete $conf->{$opt};
1272 PVE::LXC::write_config($vmid, $conf);
1273
1274 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1275
1276 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1277 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1278 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
1279
1280 if ($oldnet->{bridge}) {
1281 PVE::Network::tap_unplug($veth);
1282 foreach (qw(bridge tag firewall)) {
1283 delete $oldnet->{$_};
1284 }
1285 $conf->{$opt} = print_lxc_network($oldnet);
1286 PVE::LXC::write_config($vmid, $conf);
1287 }
1288
1289 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1290 foreach (qw(bridge tag firewall)) {
1291 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1292 }
1293 $conf->{$opt} = print_lxc_network($oldnet);
1294 PVE::LXC::write_config($vmid, $conf);
1295 }
1296 } else {
1297 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1298 }
1299
1300 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1301 }
1302
1303 sub hotplug_net {
1304 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1305
1306 my $veth = "veth${vmid}i${netid}";
1307 my $vethpeer = $veth . "p";
1308 my $eth = $newnet->{name};
1309
1310 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1311 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1312
1313 # attach peer in container
1314 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1315 PVE::Tools::run_command($cmd);
1316
1317 # link up peer in container
1318 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1319 PVE::Tools::run_command($cmd);
1320
1321 my $done = { type => 'veth' };
1322 foreach (qw(bridge tag firewall hwaddr name)) {
1323 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1324 }
1325 $conf->{$opt} = print_lxc_network($done);
1326
1327 PVE::LXC::write_config($vmid, $conf);
1328 }
1329
1330 sub update_ipconfig {
1331 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1332
1333 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1334
1335 my $optdata = parse_lxc_network($conf->{$opt});
1336 my $deleted = [];
1337 my $added = [];
1338 my $nscmd = sub {
1339 my $cmdargs = shift;
1340 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1341 };
1342 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1343
1344 my $change_ip_config = sub {
1345 my ($ipversion) = @_;
1346
1347 my $family_opt = "-$ipversion";
1348 my $suffix = $ipversion == 4 ? '' : $ipversion;
1349 my $gw= "gw$suffix";
1350 my $ip= "ip$suffix";
1351
1352 my $newip = $newnet->{$ip};
1353 my $newgw = $newnet->{$gw};
1354 my $oldip = $optdata->{$ip};
1355
1356 my $change_ip = &$safe_string_ne($oldip, $newip);
1357 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1358
1359 return if !$change_ip && !$change_gw;
1360
1361 # step 1: add new IP, if this fails we cancel
1362 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1363 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1364 if (my $err = $@) {
1365 warn $err;
1366 return;
1367 }
1368 }
1369
1370 # step 2: replace gateway
1371 # If this fails we delete the added IP and cancel.
1372 # If it succeeds we save the config and delete the old IP, ignoring
1373 # errors. The config is then saved.
1374 # Note: 'ip route replace' can add
1375 if ($change_gw) {
1376 if ($newgw) {
1377 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1378 if (my $err = $@) {
1379 warn $err;
1380 # the route was not replaced, the old IP is still available
1381 # rollback (delete new IP) and cancel
1382 if ($change_ip) {
1383 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1384 warn $@ if $@; # no need to die here
1385 }
1386 return;
1387 }
1388 } else {
1389 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1390 # if the route was not deleted, the guest might have deleted it manually
1391 # warn and continue
1392 warn $@ if $@;
1393 }
1394 }
1395
1396 # from this point on we save the configuration
1397 # step 3: delete old IP ignoring errors
1398 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1399 # We need to enable promote_secondaries, otherwise our newly added
1400 # address will be removed along with the old one.
1401 my $promote = 0;
1402 eval {
1403 if ($ipversion == 4) {
1404 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1405 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1406 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1407 }
1408 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1409 };
1410 warn $@ if $@; # no need to die here
1411
1412 if ($ipversion == 4) {
1413 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1414 }
1415 }
1416
1417 foreach my $property ($ip, $gw) {
1418 if ($newnet->{$property}) {
1419 $optdata->{$property} = $newnet->{$property};
1420 } else {
1421 delete $optdata->{$property};
1422 }
1423 }
1424 $conf->{$opt} = print_lxc_network($optdata);
1425 PVE::LXC::write_config($vmid, $conf);
1426 $lxc_setup->setup_network($conf);
1427 };
1428
1429 &$change_ip_config(4);
1430 &$change_ip_config(6);
1431
1432 }
1433
1434 # Internal snapshots
1435
1436 # NOTE: Snapshot create/delete involves several non-atomic
1437 # action, and can take a long time.
1438 # So we try to avoid locking the file and use 'lock' variable
1439 # inside the config file instead.
1440
1441 my $snapshot_copy_config = sub {
1442 my ($source, $dest) = @_;
1443
1444 foreach my $k (keys %$source) {
1445 next if $k eq 'snapshots';
1446 next if $k eq 'snapstate';
1447 next if $k eq 'snaptime';
1448 next if $k eq 'vmstate';
1449 next if $k eq 'lock';
1450 next if $k eq 'digest';
1451 next if $k eq 'description';
1452
1453 $dest->{$k} = $source->{$k};
1454 }
1455 };
1456
1457 my $snapshot_prepare = sub {
1458 my ($vmid, $snapname, $comment) = @_;
1459
1460 my $snap;
1461
1462 my $updatefn = sub {
1463
1464 my $conf = load_config($vmid);
1465
1466 die "you can't take a snapshot if it's a template\n"
1467 if is_template($conf);
1468
1469 check_lock($conf);
1470
1471 $conf->{lock} = 'snapshot';
1472
1473 die "snapshot name '$snapname' already used\n"
1474 if defined($conf->{snapshots}->{$snapname});
1475
1476 my $storecfg = PVE::Storage::config();
1477 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1478
1479 $snap = $conf->{snapshots}->{$snapname} = {};
1480
1481 &$snapshot_copy_config($conf, $snap);
1482
1483 $snap->{'snapstate'} = "prepare";
1484 $snap->{'snaptime'} = time();
1485 $snap->{'description'} = $comment if $comment;
1486 $conf->{snapshots}->{$snapname} = $snap;
1487
1488 PVE::LXC::write_config($vmid, $conf);
1489 };
1490
1491 lock_container($vmid, 10, $updatefn);
1492
1493 return $snap;
1494 };
1495
1496 my $snapshot_commit = sub {
1497 my ($vmid, $snapname) = @_;
1498
1499 my $updatefn = sub {
1500
1501 my $conf = load_config($vmid);
1502
1503 die "missing snapshot lock\n"
1504 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1505
1506 die "snapshot '$snapname' does not exist\n"
1507 if !defined($conf->{snapshots}->{$snapname});
1508
1509 die "wrong snapshot state\n"
1510 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1511 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
1512
1513 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1514 delete $conf->{lock};
1515 $conf->{parent} = $snapname;
1516
1517 PVE::LXC::write_config($vmid, $conf);
1518 };
1519
1520 lock_container($vmid, 10 ,$updatefn);
1521 };
1522
1523 sub has_feature {
1524 my ($feature, $conf, $storecfg, $snapname) = @_;
1525
1526 #Fixme add other drives if necessary.
1527 my $err;
1528
1529 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1530 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
1531
1532 return $err ? 0 : 1;
1533 }
1534
1535 sub snapshot_create {
1536 my ($vmid, $snapname, $comment) = @_;
1537
1538 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1539
1540 my $conf = load_config($vmid);
1541
1542 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1543 my $running = check_running($vmid);
1544 eval {
1545 if ($running) {
1546 PVE::Tools::run_command($cmd);
1547 };
1548
1549 my $storecfg = PVE::Storage::config();
1550 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1551 my $volid = $rootinfo->{volume};
1552
1553 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1554 if ($running) {
1555 PVE::Tools::run_command($cmd);
1556 };
1557
1558 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1559 &$snapshot_commit($vmid, $snapname);
1560 };
1561 if(my $err = $@) {
1562 snapshot_delete($vmid, $snapname, 1);
1563 die "$err\n";
1564 }
1565 }
1566
1567 sub snapshot_delete {
1568 my ($vmid, $snapname, $force) = @_;
1569
1570 my $snap;
1571
1572 my $conf;
1573
1574 my $updatefn = sub {
1575
1576 $conf = load_config($vmid);
1577
1578 die "you can't delete a snapshot if vm is a template\n"
1579 if is_template($conf);
1580
1581 $snap = $conf->{snapshots}->{$snapname};
1582
1583 check_lock($conf);
1584
1585 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1586
1587 $snap->{snapstate} = 'delete';
1588
1589 PVE::LXC::write_config($vmid, $conf);
1590 };
1591
1592 lock_container($vmid, 10, $updatefn);
1593
1594 my $storecfg = PVE::Storage::config();
1595
1596 my $del_snap = sub {
1597
1598 check_lock($conf);
1599
1600 if ($conf->{parent} eq $snapname) {
1601 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1602 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
1603 } else {
1604 delete $conf->{parent};
1605 }
1606 }
1607
1608 delete $conf->{snapshots}->{$snapname};
1609
1610 PVE::LXC::write_config($vmid, $conf);
1611 };
1612
1613 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1614 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1615 my $volid = $rootinfo->{volume};
1616
1617 eval {
1618 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1619 };
1620 my $err = $@;
1621
1622 if(!$err || ($err && $force)) {
1623 lock_container($vmid, 10, $del_snap);
1624 if ($err) {
1625 die "Can't delete snapshot: $vmid $snapname $err\n";
1626 }
1627 }
1628 }
1629
1630 sub snapshot_rollback {
1631 my ($vmid, $snapname) = @_;
1632
1633 my $storecfg = PVE::Storage::config();
1634
1635 my $conf = load_config($vmid);
1636
1637 die "you can't rollback if vm is a template\n" if is_template($conf);
1638
1639 my $snap = $conf->{snapshots}->{$snapname};
1640
1641 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1642
1643 my $rootfs = $snap->{rootfs};
1644 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1645 my $volid = $rootinfo->{volume};
1646
1647 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
1648
1649 my $updatefn = sub {
1650
1651 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1652 if $snap->{snapstate};
1653
1654 check_lock($conf);
1655
1656 system("lxc-stop -n $vmid --kill") if check_running($vmid);
1657
1658 die "unable to rollback vm $vmid: vm is running\n"
1659 if check_running($vmid);
1660
1661 $conf->{lock} = 'rollback';
1662
1663 my $forcemachine;
1664
1665 # copy snapshot config to current config
1666
1667 my $tmp_conf = $conf;
1668 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
1669 $conf->{snapshots} = $tmp_conf->{snapshots};
1670 delete $conf->{snaptime};
1671 delete $conf->{snapname};
1672 $conf->{parent} = $snapname;
1673
1674 PVE::LXC::write_config($vmid, $conf);
1675 };
1676
1677 my $unlockfn = sub {
1678 delete $conf->{lock};
1679 PVE::LXC::write_config($vmid, $conf);
1680 };
1681
1682 lock_container($vmid, 10, $updatefn);
1683
1684 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
1685
1686 lock_container($vmid, 5, $unlockfn);
1687 }
1688
1689 sub template_create {
1690 my ($vmid, $conf) = @_;
1691
1692 my $storecfg = PVE::Storage::config();
1693
1694 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1695 my $volid = $rootinfo->{volume};
1696
1697 die "Template feature is not available for '$volid'\n"
1698 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1699
1700 PVE::Storage::activate_volumes($storecfg, [$volid]);
1701
1702 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1703 $rootinfo->{volume} = $template_volid;
1704 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
1705
1706 write_config($vmid, $conf);
1707 }
1708
1709 sub is_template {
1710 my ($conf) = @_;
1711
1712 return 1 if defined $conf->{template} && $conf->{template} == 1;
1713 }
1714
1715 1;