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