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