]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
mountpoint_mount: return info if mount uses loop device
[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 my $format_size = sub {
759 my ($size) = @_;
760
761 $size = int($size);
762
763 my $kb = int($size/1024);
764 return $size if $kb*1024 != $size;
765
766 my $mb = int($kb/1024);
767 return "${kb}K" if $mb*1024 != $kb;
768
769 my $gb = int($mb/1024);
770 return "${mb}M" if $gb*1024 != $mb;
771
772 return "${gb}G";
773 };
774
775 sub parse_ct_mountpoint {
776 my ($data) = @_;
777
778 $data //= '';
779
780 my $res = {};
781
782 foreach my $p (split (/,/, $data)) {
783 next if $p =~ m/^\s*$/;
784
785 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
786 my ($k, $v) = ($1, $2);
787 return undef if defined($res->{$k});
788 $res->{$k} = $v;
789 } else {
790 if (!$res->{volume} && $p !~ m/=/) {
791 $res->{volume} = $p;
792 } else {
793 return undef;
794 }
795 }
796 }
797
798 return undef if !defined($res->{volume});
799
800 return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
801
802 if ($res->{size}) {
803 return undef if !defined($res->{size} = &$parse_size($res->{size}));
804 }
805
806 return $res;
807 }
808
809 sub print_ct_mountpoint {
810 my ($info, $nomp) = @_;
811
812 my $opts = '';
813
814 die "missing volume\n" if !$info->{volume};
815
816 foreach my $o (qw(backup)) {
817 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
818 }
819
820 if ($info->{size}) {
821 $opts .= ",size=" . &$format_size($info->{size});
822 }
823
824 $opts .= ",mp=$info->{mp}" if !$nomp;
825
826 return "$info->{volume}$opts";
827 }
828
829 sub print_lxc_network {
830 my $net = shift;
831
832 die "no network name defined\n" if !$net->{name};
833
834 my $res = "name=$net->{name}";
835
836 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
837 next if !defined($net->{$k});
838 $res .= ",$k=$net->{$k}";
839 }
840
841 return $res;
842 }
843
844 sub parse_lxc_network {
845 my ($data) = @_;
846
847 my $res = {};
848
849 return $res if !$data;
850
851 foreach my $pv (split (/,/, $data)) {
852 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
853 $res->{$1} = $2;
854 } else {
855 return undef;
856 }
857 }
858
859 $res->{type} = 'veth';
860 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
861
862 return $res;
863 }
864
865 sub read_cgroup_value {
866 my ($group, $vmid, $name, $full) = @_;
867
868 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
869
870 return PVE::Tools::file_get_contents($path) if $full;
871
872 return PVE::Tools::file_read_firstline($path);
873 }
874
875 sub write_cgroup_value {
876 my ($group, $vmid, $name, $value) = @_;
877
878 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
879 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
880
881 }
882
883 sub find_lxc_console_pids {
884
885 my $res = {};
886
887 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
888 my ($pid) = @_;
889
890 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
891 return if !$cmdline;
892
893 my @args = split(/\0/, $cmdline);
894
895 # serach for lxc-console -n <vmid>
896 return if scalar(@args) != 3;
897 return if $args[1] ne '-n';
898 return if $args[2] !~ m/^\d+$/;
899 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
900
901 my $vmid = $args[2];
902
903 push @{$res->{$vmid}}, $pid;
904 });
905
906 return $res;
907 }
908
909 sub find_lxc_pid {
910 my ($vmid) = @_;
911
912 my $pid = undef;
913 my $parser = sub {
914 my $line = shift;
915 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
916 };
917 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
918
919 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
920
921 return $pid;
922 }
923
924 my $ipv4_reverse_mask = [
925 '0.0.0.0',
926 '128.0.0.0',
927 '192.0.0.0',
928 '224.0.0.0',
929 '240.0.0.0',
930 '248.0.0.0',
931 '252.0.0.0',
932 '254.0.0.0',
933 '255.0.0.0',
934 '255.128.0.0',
935 '255.192.0.0',
936 '255.224.0.0',
937 '255.240.0.0',
938 '255.248.0.0',
939 '255.252.0.0',
940 '255.254.0.0',
941 '255.255.0.0',
942 '255.255.128.0',
943 '255.255.192.0',
944 '255.255.224.0',
945 '255.255.240.0',
946 '255.255.248.0',
947 '255.255.252.0',
948 '255.255.254.0',
949 '255.255.255.0',
950 '255.255.255.128',
951 '255.255.255.192',
952 '255.255.255.224',
953 '255.255.255.240',
954 '255.255.255.248',
955 '255.255.255.252',
956 '255.255.255.254',
957 '255.255.255.255',
958 ];
959
960 # Note: we cannot use Net:IP, because that only allows strict
961 # CIDR networks
962 sub parse_ipv4_cidr {
963 my ($cidr, $noerr) = @_;
964
965 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
966 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
967 }
968
969 return undef if $noerr;
970
971 die "unable to parse ipv4 address/mask\n";
972 }
973
974 sub check_lock {
975 my ($conf) = @_;
976
977 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
978 }
979
980 sub update_lxc_config {
981 my ($storage_cfg, $vmid, $conf) = @_;
982
983 my $dir = "/var/lib/lxc/$vmid";
984
985 if ($conf->{template}) {
986
987 unlink "$dir/config";
988
989 return;
990 }
991
992 my $raw = '';
993
994 die "missing 'arch' - internal error" if !$conf->{arch};
995 $raw .= "lxc.arch = $conf->{arch}\n";
996
997 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
998 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
999 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1000 } else {
1001 die "implement me";
1002 }
1003
1004 if (!has_dev_console($conf)) {
1005 $raw .= "lxc.console = none\n";
1006 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1007 }
1008
1009 my $ttycount = get_tty_count($conf);
1010 $raw .= "lxc.tty = $ttycount\n";
1011
1012 my $utsname = $conf->{hostname} || "CT$vmid";
1013 $raw .= "lxc.utsname = $utsname\n";
1014
1015 my $memory = $conf->{memory} || 512;
1016 my $swap = $conf->{swap} // 0;
1017
1018 my $lxcmem = int($memory*1024*1024);
1019 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1020
1021 my $lxcswap = int(($memory + $swap)*1024*1024);
1022 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1023
1024 if (my $cpulimit = $conf->{cpulimit}) {
1025 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1026 my $value = int(100000*$cpulimit);
1027 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1028 }
1029
1030 my $shares = $conf->{cpuunits} || 1024;
1031 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1032
1033 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
1034 $mountpoint->{mp} = '/';
1035
1036 my ($path, $use_loopdev) = mountpoint_mount_path($mountpoint, $storage_cfg);
1037 $path = "loop:$path" if $use_loopdev;
1038
1039 $raw .= "lxc.rootfs = $path\n";
1040
1041 my $netcount = 0;
1042 foreach my $k (keys %$conf) {
1043 next if $k !~ m/^net(\d+)$/;
1044 my $ind = $1;
1045 my $d = parse_lxc_network($conf->{$k});
1046 $netcount++;
1047 $raw .= "lxc.network.type = veth\n";
1048 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1049 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1050 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1051 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
1052 }
1053
1054 if (my $lxcconf = $conf->{lxc}) {
1055 foreach my $entry (@$lxcconf) {
1056 my ($k, $v) = @$entry;
1057 $netcount++ if $k eq 'lxc.network.type';
1058 $raw .= "$k = $v\n";
1059 }
1060 }
1061
1062 $raw .= "lxc.network.type = empty\n" if !$netcount;
1063
1064 File::Path::mkpath("$dir/rootfs");
1065
1066 PVE::Tools::file_set_contents("$dir/config", $raw);
1067 }
1068
1069 # verify and cleanup nameserver list (replace \0 with ' ')
1070 sub verify_nameserver_list {
1071 my ($nameserver_list) = @_;
1072
1073 my @list = ();
1074 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1075 PVE::JSONSchema::pve_verify_ip($server);
1076 push @list, $server;
1077 }
1078
1079 return join(' ', @list);
1080 }
1081
1082 sub verify_searchdomain_list {
1083 my ($searchdomain_list) = @_;
1084
1085 my @list = ();
1086 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1087 # todo: should we add checks for valid dns domains?
1088 push @list, $server;
1089 }
1090
1091 return join(' ', @list);
1092 }
1093
1094 sub update_pct_config {
1095 my ($vmid, $conf, $running, $param, $delete) = @_;
1096
1097 my @nohotplug;
1098
1099 my $new_disks = [];
1100
1101 my $rootdir;
1102 if ($running) {
1103 my $pid = find_lxc_pid($vmid);
1104 $rootdir = "/proc/$pid/root";
1105 }
1106
1107 if (defined($delete)) {
1108 foreach my $opt (@$delete) {
1109 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1110 die "unable to delete required option '$opt'\n";
1111 } elsif ($opt eq 'swap') {
1112 delete $conf->{$opt};
1113 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1114 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1115 delete $conf->{$opt};
1116 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1117 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1118 delete $conf->{$opt};
1119 push @nohotplug, $opt;
1120 next if $running;
1121 } elsif ($opt =~ m/^net(\d)$/) {
1122 delete $conf->{$opt};
1123 next if !$running;
1124 my $netid = $1;
1125 PVE::Network::veth_delete("veth${vmid}i$netid");
1126 } elsif ($opt eq 'protection') {
1127 delete $conf->{$opt};
1128 } elsif ($opt =~ m/^mp(\d+)$/) {
1129 delete $conf->{$opt};
1130 push @nohotplug, $opt;
1131 next if $running;
1132 } elsif ($opt eq 'rootfs') {
1133 die "implement me"
1134 } else {
1135 die "implement me"
1136 }
1137 write_config($vmid, $conf) if $running;
1138 }
1139 }
1140
1141 # There's no separate swap size to configure, there's memory and "total"
1142 # memory (iow. memory+swap). This means we have to change them together.
1143 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1144 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
1145 if (defined($wanted_memory) || defined($wanted_swap)) {
1146
1147 $wanted_memory //= ($conf->{memory} || 512);
1148 $wanted_swap //= ($conf->{swap} || 0);
1149
1150 my $total = $wanted_memory + $wanted_swap;
1151 if ($running) {
1152 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1153 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1154 }
1155 $conf->{memory} = $wanted_memory;
1156 $conf->{swap} = $wanted_swap;
1157
1158 write_config($vmid, $conf) if $running;
1159 }
1160
1161 foreach my $opt (keys %$param) {
1162 my $value = $param->{$opt};
1163 if ($opt eq 'hostname') {
1164 $conf->{$opt} = $value;
1165 } elsif ($opt eq 'onboot') {
1166 $conf->{$opt} = $value ? 1 : 0;
1167 } elsif ($opt eq 'startup') {
1168 $conf->{$opt} = $value;
1169 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1170 $conf->{$opt} = $value;
1171 push @nohotplug, $opt;
1172 next if $running;
1173 } elsif ($opt eq 'nameserver') {
1174 my $list = verify_nameserver_list($value);
1175 $conf->{$opt} = $list;
1176 push @nohotplug, $opt;
1177 next if $running;
1178 } elsif ($opt eq 'searchdomain') {
1179 my $list = verify_searchdomain_list($value);
1180 $conf->{$opt} = $list;
1181 push @nohotplug, $opt;
1182 next if $running;
1183 } elsif ($opt eq 'cpulimit') {
1184 $conf->{$opt} = $value;
1185 push @nohotplug, $opt; # fixme: hotplug
1186 next;
1187 } elsif ($opt eq 'cpuunits') {
1188 $conf->{$opt} = $value;
1189 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1190 } elsif ($opt eq 'description') {
1191 $conf->{$opt} = PVE::Tools::encode_text($value);
1192 } elsif ($opt =~ m/^net(\d+)$/) {
1193 my $netid = $1;
1194 my $net = parse_lxc_network($value);
1195 if (!$running) {
1196 $conf->{$opt} = print_lxc_network($net);
1197 } else {
1198 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1199 }
1200 } elsif ($opt eq 'protection') {
1201 $conf->{$opt} = $value ? 1 : 0;
1202 } elsif ($opt =~ m/^mp(\d+)$/) {
1203 $conf->{$opt} = $value;
1204 push @$new_disks, $opt;
1205 push @nohotplug, $opt;
1206 next;
1207 } elsif ($opt eq 'rootfs') {
1208 die "implement me: $opt";
1209 } else {
1210 die "implement me: $opt";
1211 }
1212 write_config($vmid, $conf) if $running;
1213 }
1214
1215 if ($running && scalar(@nohotplug)) {
1216 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1217 }
1218
1219 if (@$new_disks) {
1220 my $storage_cfg = PVE::Storage::config();
1221 create_disks($storage_cfg, $vmid, $conf, $conf);
1222 mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
1223 umount_all($vmid, $storage_cfg, $conf, 0);
1224 }
1225 }
1226
1227 sub has_dev_console {
1228 my ($conf) = @_;
1229
1230 return !(defined($conf->{console}) && !$conf->{console});
1231 }
1232
1233 sub get_tty_count {
1234 my ($conf) = @_;
1235
1236 return $conf->{tty} // $confdesc->{tty}->{default};
1237 }
1238
1239 sub get_cmode {
1240 my ($conf) = @_;
1241
1242 return $conf->{cmode} // $confdesc->{cmode}->{default};
1243 }
1244
1245 sub get_console_command {
1246 my ($vmid, $conf) = @_;
1247
1248 my $cmode = get_cmode($conf);
1249
1250 if ($cmode eq 'console') {
1251 return ['lxc-console', '-n', $vmid, '-t', 0];
1252 } elsif ($cmode eq 'tty') {
1253 return ['lxc-console', '-n', $vmid];
1254 } elsif ($cmode eq 'shell') {
1255 return ['lxc-attach', '--clear-env', '-n', $vmid];
1256 } else {
1257 die "internal error";
1258 }
1259 }
1260
1261 sub get_primary_ips {
1262 my ($conf) = @_;
1263
1264 # return data from net0
1265
1266 return undef if !defined($conf->{net0});
1267 my $net = parse_lxc_network($conf->{net0});
1268
1269 my $ipv4 = $net->{ip};
1270 if ($ipv4) {
1271 if ($ipv4 =~ /^(dhcp|manual)$/) {
1272 $ipv4 = undef
1273 } else {
1274 $ipv4 =~ s!/\d+$!!;
1275 }
1276 }
1277 my $ipv6 = $net->{ip6};
1278 if ($ipv6) {
1279 if ($ipv6 =~ /^(dhcp|manual)$/) {
1280 $ipv6 = undef;
1281 } else {
1282 $ipv6 =~ s!/\d+$!!;
1283 }
1284 }
1285
1286 return ($ipv4, $ipv6);
1287 }
1288
1289
1290 sub destroy_lxc_container {
1291 my ($storage_cfg, $vmid, $conf) = @_;
1292
1293 foreach_mountpoint($conf, sub {
1294 my ($ms, $mountpoint) = @_;
1295 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
1296 PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
1297 });
1298
1299 rmdir "/var/lib/lxc/$vmid/rootfs";
1300 unlink "/var/lib/lxc/$vmid/config";
1301 rmdir "/var/lib/lxc/$vmid";
1302 destroy_config($vmid);
1303
1304 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1305 #PVE::Tools::run_command($cmd);
1306 }
1307
1308 sub vm_stop_cleanup {
1309 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1310
1311 eval {
1312 if (!$keepActive) {
1313
1314 my $vollist = get_vm_volumes($conf);
1315 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
1316 }
1317 };
1318 warn $@ if $@; # avoid errors - just warn
1319 }
1320
1321 my $safe_num_ne = sub {
1322 my ($a, $b) = @_;
1323
1324 return 0 if !defined($a) && !defined($b);
1325 return 1 if !defined($a);
1326 return 1 if !defined($b);
1327
1328 return $a != $b;
1329 };
1330
1331 my $safe_string_ne = sub {
1332 my ($a, $b) = @_;
1333
1334 return 0 if !defined($a) && !defined($b);
1335 return 1 if !defined($a);
1336 return 1 if !defined($b);
1337
1338 return $a ne $b;
1339 };
1340
1341 sub update_net {
1342 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1343
1344 if ($newnet->{type} ne 'veth') {
1345 # for when there are physical interfaces
1346 die "cannot update interface of type $newnet->{type}";
1347 }
1348
1349 my $veth = "veth${vmid}i${netid}";
1350 my $eth = $newnet->{name};
1351
1352 if (my $oldnetcfg = $conf->{$opt}) {
1353 my $oldnet = parse_lxc_network($oldnetcfg);
1354
1355 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1356 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
1357
1358 PVE::Network::veth_delete($veth);
1359 delete $conf->{$opt};
1360 write_config($vmid, $conf);
1361
1362 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1363
1364 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1365 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1366 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
1367
1368 if ($oldnet->{bridge}) {
1369 PVE::Network::tap_unplug($veth);
1370 foreach (qw(bridge tag firewall)) {
1371 delete $oldnet->{$_};
1372 }
1373 $conf->{$opt} = print_lxc_network($oldnet);
1374 write_config($vmid, $conf);
1375 }
1376
1377 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1378 foreach (qw(bridge tag firewall)) {
1379 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1380 }
1381 $conf->{$opt} = print_lxc_network($oldnet);
1382 write_config($vmid, $conf);
1383 }
1384 } else {
1385 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1386 }
1387
1388 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1389 }
1390
1391 sub hotplug_net {
1392 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1393
1394 my $veth = "veth${vmid}i${netid}";
1395 my $vethpeer = $veth . "p";
1396 my $eth = $newnet->{name};
1397
1398 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1399 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1400
1401 # attach peer in container
1402 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1403 PVE::Tools::run_command($cmd);
1404
1405 # link up peer in container
1406 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1407 PVE::Tools::run_command($cmd);
1408
1409 my $done = { type => 'veth' };
1410 foreach (qw(bridge tag firewall hwaddr name)) {
1411 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1412 }
1413 $conf->{$opt} = print_lxc_network($done);
1414
1415 write_config($vmid, $conf);
1416 }
1417
1418 sub update_ipconfig {
1419 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1420
1421 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
1422
1423 my $optdata = parse_lxc_network($conf->{$opt});
1424 my $deleted = [];
1425 my $added = [];
1426 my $nscmd = sub {
1427 my $cmdargs = shift;
1428 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1429 };
1430 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1431
1432 my $change_ip_config = sub {
1433 my ($ipversion) = @_;
1434
1435 my $family_opt = "-$ipversion";
1436 my $suffix = $ipversion == 4 ? '' : $ipversion;
1437 my $gw= "gw$suffix";
1438 my $ip= "ip$suffix";
1439
1440 my $newip = $newnet->{$ip};
1441 my $newgw = $newnet->{$gw};
1442 my $oldip = $optdata->{$ip};
1443
1444 my $change_ip = &$safe_string_ne($oldip, $newip);
1445 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1446
1447 return if !$change_ip && !$change_gw;
1448
1449 # step 1: add new IP, if this fails we cancel
1450 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1451 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1452 if (my $err = $@) {
1453 warn $err;
1454 return;
1455 }
1456 }
1457
1458 # step 2: replace gateway
1459 # If this fails we delete the added IP and cancel.
1460 # If it succeeds we save the config and delete the old IP, ignoring
1461 # errors. The config is then saved.
1462 # Note: 'ip route replace' can add
1463 if ($change_gw) {
1464 if ($newgw) {
1465 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1466 if (my $err = $@) {
1467 warn $err;
1468 # the route was not replaced, the old IP is still available
1469 # rollback (delete new IP) and cancel
1470 if ($change_ip) {
1471 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1472 warn $@ if $@; # no need to die here
1473 }
1474 return;
1475 }
1476 } else {
1477 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1478 # if the route was not deleted, the guest might have deleted it manually
1479 # warn and continue
1480 warn $@ if $@;
1481 }
1482 }
1483
1484 # from this point on we save the configuration
1485 # step 3: delete old IP ignoring errors
1486 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1487 # We need to enable promote_secondaries, otherwise our newly added
1488 # address will be removed along with the old one.
1489 my $promote = 0;
1490 eval {
1491 if ($ipversion == 4) {
1492 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1493 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1494 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1495 }
1496 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1497 };
1498 warn $@ if $@; # no need to die here
1499
1500 if ($ipversion == 4) {
1501 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1502 }
1503 }
1504
1505 foreach my $property ($ip, $gw) {
1506 if ($newnet->{$property}) {
1507 $optdata->{$property} = $newnet->{$property};
1508 } else {
1509 delete $optdata->{$property};
1510 }
1511 }
1512 $conf->{$opt} = print_lxc_network($optdata);
1513 write_config($vmid, $conf);
1514 $lxc_setup->setup_network($conf);
1515 };
1516
1517 &$change_ip_config(4);
1518 &$change_ip_config(6);
1519
1520 }
1521
1522 # Internal snapshots
1523
1524 # NOTE: Snapshot create/delete involves several non-atomic
1525 # action, and can take a long time.
1526 # So we try to avoid locking the file and use 'lock' variable
1527 # inside the config file instead.
1528
1529 my $snapshot_copy_config = sub {
1530 my ($source, $dest) = @_;
1531
1532 foreach my $k (keys %$source) {
1533 next if $k eq 'snapshots';
1534 next if $k eq 'snapstate';
1535 next if $k eq 'snaptime';
1536 next if $k eq 'vmstate';
1537 next if $k eq 'lock';
1538 next if $k eq 'digest';
1539 next if $k eq 'description';
1540
1541 $dest->{$k} = $source->{$k};
1542 }
1543 };
1544
1545 my $snapshot_prepare = sub {
1546 my ($vmid, $snapname, $comment) = @_;
1547
1548 my $snap;
1549
1550 my $updatefn = sub {
1551
1552 my $conf = load_config($vmid);
1553
1554 die "you can't take a snapshot if it's a template\n"
1555 if is_template($conf);
1556
1557 check_lock($conf);
1558
1559 $conf->{lock} = 'snapshot';
1560
1561 die "snapshot name '$snapname' already used\n"
1562 if defined($conf->{snapshots}->{$snapname});
1563
1564 my $storecfg = PVE::Storage::config();
1565 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1566
1567 $snap = $conf->{snapshots}->{$snapname} = {};
1568
1569 &$snapshot_copy_config($conf, $snap);
1570
1571 $snap->{'snapstate'} = "prepare";
1572 $snap->{'snaptime'} = time();
1573 $snap->{'description'} = $comment if $comment;
1574 $conf->{snapshots}->{$snapname} = $snap;
1575
1576 write_config($vmid, $conf);
1577 };
1578
1579 lock_container($vmid, 10, $updatefn);
1580
1581 return $snap;
1582 };
1583
1584 my $snapshot_commit = sub {
1585 my ($vmid, $snapname) = @_;
1586
1587 my $updatefn = sub {
1588
1589 my $conf = load_config($vmid);
1590
1591 die "missing snapshot lock\n"
1592 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1593
1594 die "snapshot '$snapname' does not exist\n"
1595 if !defined($conf->{snapshots}->{$snapname});
1596
1597 die "wrong snapshot state\n"
1598 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1599 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
1600
1601 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1602 delete $conf->{lock};
1603 $conf->{parent} = $snapname;
1604
1605 write_config($vmid, $conf);
1606 };
1607
1608 lock_container($vmid, 10 ,$updatefn);
1609 };
1610
1611 sub has_feature {
1612 my ($feature, $conf, $storecfg, $snapname) = @_;
1613
1614 my $err;
1615
1616 foreach_mountpoint($conf, sub {
1617 my ($ms, $mountpoint) = @_;
1618
1619 return if $err; # skip further test
1620
1621 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1622
1623 # TODO: implement support for mountpoints
1624 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1625 if $ms ne 'rootfs';
1626 });
1627
1628 return $err ? 0 : 1;
1629 }
1630
1631 sub snapshot_create {
1632 my ($vmid, $snapname, $comment) = @_;
1633
1634 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1635
1636 my $conf = load_config($vmid);
1637
1638 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1639 my $running = check_running($vmid);
1640 eval {
1641 if ($running) {
1642 PVE::Tools::run_command($cmd);
1643 };
1644
1645 my $storecfg = PVE::Storage::config();
1646 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
1647 my $volid = $rootinfo->{volume};
1648
1649 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1650 if ($running) {
1651 PVE::Tools::run_command($cmd);
1652 };
1653
1654 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1655 &$snapshot_commit($vmid, $snapname);
1656 };
1657 if(my $err = $@) {
1658 snapshot_delete($vmid, $snapname, 1);
1659 die "$err\n";
1660 }
1661 }
1662
1663 sub snapshot_delete {
1664 my ($vmid, $snapname, $force) = @_;
1665
1666 my $snap;
1667
1668 my $conf;
1669
1670 my $updatefn = sub {
1671
1672 $conf = load_config($vmid);
1673
1674 die "you can't delete a snapshot if vm is a template\n"
1675 if is_template($conf);
1676
1677 $snap = $conf->{snapshots}->{$snapname};
1678
1679 check_lock($conf);
1680
1681 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1682
1683 $snap->{snapstate} = 'delete';
1684
1685 write_config($vmid, $conf);
1686 };
1687
1688 lock_container($vmid, 10, $updatefn);
1689
1690 my $storecfg = PVE::Storage::config();
1691
1692 my $del_snap = sub {
1693
1694 check_lock($conf);
1695
1696 if ($conf->{parent} eq $snapname) {
1697 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1698 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
1699 } else {
1700 delete $conf->{parent};
1701 }
1702 }
1703
1704 delete $conf->{snapshots}->{$snapname};
1705
1706 write_config($vmid, $conf);
1707 };
1708
1709 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1710 my $rootinfo = parse_ct_mountpoint($rootfs);
1711 my $volid = $rootinfo->{volume};
1712
1713 eval {
1714 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1715 };
1716 my $err = $@;
1717
1718 if(!$err || ($err && $force)) {
1719 lock_container($vmid, 10, $del_snap);
1720 if ($err) {
1721 die "Can't delete snapshot: $vmid $snapname $err\n";
1722 }
1723 }
1724 }
1725
1726 sub snapshot_rollback {
1727 my ($vmid, $snapname) = @_;
1728
1729 my $storecfg = PVE::Storage::config();
1730
1731 my $conf = load_config($vmid);
1732
1733 die "you can't rollback if vm is a template\n" if is_template($conf);
1734
1735 my $snap = $conf->{snapshots}->{$snapname};
1736
1737 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1738
1739 my $rootfs = $snap->{rootfs};
1740 my $rootinfo = parse_ct_mountpoint($rootfs);
1741 my $volid = $rootinfo->{volume};
1742
1743 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
1744
1745 my $updatefn = sub {
1746
1747 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1748 if $snap->{snapstate};
1749
1750 check_lock($conf);
1751
1752 system("lxc-stop -n $vmid --kill") if check_running($vmid);
1753
1754 die "unable to rollback vm $vmid: vm is running\n"
1755 if check_running($vmid);
1756
1757 $conf->{lock} = 'rollback';
1758
1759 my $forcemachine;
1760
1761 # copy snapshot config to current config
1762
1763 my $tmp_conf = $conf;
1764 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
1765 $conf->{snapshots} = $tmp_conf->{snapshots};
1766 delete $conf->{snaptime};
1767 delete $conf->{snapname};
1768 $conf->{parent} = $snapname;
1769
1770 write_config($vmid, $conf);
1771 };
1772
1773 my $unlockfn = sub {
1774 delete $conf->{lock};
1775 write_config($vmid, $conf);
1776 };
1777
1778 lock_container($vmid, 10, $updatefn);
1779
1780 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
1781
1782 lock_container($vmid, 5, $unlockfn);
1783 }
1784
1785 sub template_create {
1786 my ($vmid, $conf) = @_;
1787
1788 my $storecfg = PVE::Storage::config();
1789
1790 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
1791 my $volid = $rootinfo->{volume};
1792
1793 die "Template feature is not available for '$volid'\n"
1794 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1795
1796 PVE::Storage::activate_volumes($storecfg, [$volid]);
1797
1798 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1799 $rootinfo->{volume} = $template_volid;
1800 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
1801
1802 write_config($vmid, $conf);
1803 }
1804
1805 sub is_template {
1806 my ($conf) = @_;
1807
1808 return 1 if defined $conf->{template} && $conf->{template} == 1;
1809 }
1810
1811 sub mountpoint_names {
1812 my ($reverse) = @_;
1813
1814 my @names = ('rootfs');
1815
1816 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1817 push @names, "mp$i";
1818 }
1819
1820 return $reverse ? reverse @names : @names;
1821 }
1822
1823 sub foreach_mountpoint_full {
1824 my ($conf, $reverse, $func) = @_;
1825
1826 foreach my $key (mountpoint_names($reverse)) {
1827 my $value = $conf->{$key};
1828 next if !defined($value);
1829 my $mountpoint = parse_ct_mountpoint($value);
1830 $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
1831 &$func($key, $mountpoint);
1832 }
1833 }
1834
1835 sub foreach_mountpoint {
1836 my ($conf, $func) = @_;
1837
1838 foreach_mountpoint_full($conf, 0, $func);
1839 }
1840
1841 sub foreach_mountpoint_reverse {
1842 my ($conf, $func) = @_;
1843
1844 foreach_mountpoint_full($conf, 1, $func);
1845 }
1846
1847 sub check_ct_modify_config_perm {
1848 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1849
1850 return 1 if $authuser ne 'root@pam';
1851
1852 foreach my $opt (@$key_list) {
1853
1854 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1855 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1856 } elsif ($opt eq 'disk') {
1857 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1858 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1859 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1860 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1861 $opt eq 'searchdomain' || $opt eq 'hostname') {
1862 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1863 } else {
1864 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1865 }
1866 }
1867
1868 return 1;
1869 }
1870
1871 sub umount_all {
1872 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1873
1874 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1875 my $volid_list = get_vm_volumes($conf);
1876
1877 foreach_mountpoint_reverse($conf, sub {
1878 my ($ms, $mountpoint) = @_;
1879
1880 my $volid = $mountpoint->{volume};
1881 my $mount = $mountpoint->{mp};
1882
1883 return if !$volid || !$mount;
1884
1885 my $mount_path = "$rootdir/$mount";
1886 $mount_path =~ s!/+!/!g;
1887
1888 return if !PVE::ProcFSTools::is_mounted($mount_path);
1889
1890 eval {
1891 PVE::Tools::run_command(['umount', '-d', $mount_path]);
1892 };
1893 if (my $err = $@) {
1894 if ($noerr) {
1895 warn $err;
1896 } else {
1897 die $err;
1898 }
1899 }
1900 });
1901 }
1902
1903 sub mount_all {
1904 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
1905
1906 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1907 File::Path::make_path($rootdir);
1908
1909 my $volid_list = get_vm_volumes($conf);
1910 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
1911
1912 eval {
1913 foreach_mountpoint($conf, sub {
1914 my ($ms, $mountpoint) = @_;
1915
1916 my $volid = $mountpoint->{volume};
1917 my $mount = $mountpoint->{mp};
1918
1919 return if !$volid || !$mount;
1920
1921 my $image_path = PVE::Storage::path($storage_cfg, $volid);
1922 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1923 PVE::Storage::parse_volname($storage_cfg, $volid);
1924
1925 die "unable to mount base volume - internal error" if $isBase;
1926
1927 File::Path::make_path "$rootdir/$mount" if $mkdirs;
1928 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
1929 });
1930 };
1931 if (my $err = $@) {
1932 warn "mounting container failed - $err";
1933 umount_all($vmid, $storage_cfg, $conf, 1);
1934 }
1935
1936 return $rootdir;
1937 }
1938
1939
1940 sub mountpoint_mount_path {
1941 my ($mountpoint, $storage_cfg, $snapname) = @_;
1942
1943 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
1944 }
1945
1946 # use $rootdir = undef to just return the corresponding mount path
1947 sub mountpoint_mount {
1948 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1949
1950 my $volid = $mountpoint->{volume};
1951 my $mount = $mountpoint->{mp};
1952
1953 return if !$volid || !$mount;
1954
1955 my $mount_path;
1956
1957 if (defined($rootdir)) {
1958 $rootdir =~ s!/+$!!;
1959 $mount_path = "$rootdir/$mount";
1960 $mount_path =~ s!/+!/!g;
1961 File::Path::mkpath($mount_path);
1962 }
1963
1964 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1965
1966 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1967
1968 if ($storage) {
1969
1970 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1971 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
1972
1973 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1974 PVE::Storage::parse_volname($storage_cfg, $volid);
1975
1976 if ($format eq 'subvol') {
1977 if ($mount_path) {
1978 if ($snapname) {
1979 if ($scfg->{type} eq 'zfspool') {
1980 my $path_arg = $path;
1981 $path_arg =~ s!^/+!!;
1982 PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
1983 } else {
1984 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1985 }
1986 } else {
1987 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
1988 }
1989 }
1990 return wantarray ? ($path, 0) : $path;
1991 } elsif ($format eq 'raw') {
1992 my $use_loopdev = 0;
1993 my @extra_opts;
1994 if ($scfg->{path}) {
1995 push @extra_opts, '-o', 'loop';
1996 $use_loopdev = 1;
1997 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1998 # do nothing
1999 } else {
2000 die "unsupported storage type '$scfg->{type}'\n";
2001 }
2002 if ($mount_path) {
2003 if ($isBase || defined($snapname)) {
2004 PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2005 } else {
2006 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2007 }
2008 }
2009 return wantarray ? ($path, $use_loopdev) : $path;
2010 } else {
2011 die "unsupported image format '$format'\n";
2012 }
2013 } elsif ($volid =~ m|^/dev/.+|) {
2014 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
2015 return wantarray ? ($volid, 0) : $volid;
2016 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2017 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2018 return wantarray ? ($volid, 0) : $volid;
2019 }
2020
2021 die "unsupported storage";
2022 }
2023
2024 sub get_vm_volumes {
2025 my ($conf, $excludes) = @_;
2026
2027 my $vollist = [];
2028
2029 foreach_mountpoint($conf, sub {
2030 my ($ms, $mountpoint) = @_;
2031
2032 return if $excludes && $ms eq $excludes;
2033
2034 my $volid = $mountpoint->{volume};
2035
2036 return if !$volid || $volid =~ m|^/|;
2037
2038 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2039 return if !$sid;
2040
2041 push @$vollist, $volid;
2042 });
2043
2044 return $vollist;
2045 }
2046
2047 sub mkfs {
2048 my ($dev) = @_;
2049
2050 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2051 }
2052
2053 sub format_disk {
2054 my ($storage_cfg, $volid) = @_;
2055
2056 if ($volid =~ m!^/dev/.+!) {
2057 mkfs($volid);
2058 return;
2059 }
2060
2061 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2062
2063 die "cannot format volume '$volid' with no storage\n" if !$storage;
2064
2065 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2066
2067 my $path = PVE::Storage::path($storage_cfg, $volid);
2068
2069 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2070 PVE::Storage::parse_volname($storage_cfg, $volid);
2071
2072 die "cannot format volume '$volid' (format == $format)\n"
2073 if $format ne 'raw';
2074
2075 mkfs($path);
2076 }
2077
2078 sub destroy_disks {
2079 my ($storecfg, $vollist) = @_;
2080
2081 foreach my $volid (@$vollist) {
2082 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2083 warn $@ if $@;
2084 }
2085 }
2086
2087 sub create_disks {
2088 my ($storecfg, $vmid, $settings, $conf) = @_;
2089
2090 my $vollist = [];
2091
2092 eval {
2093 foreach_mountpoint($settings, sub {
2094 my ($ms, $mountpoint) = @_;
2095
2096 my $volid = $mountpoint->{volume};
2097 my $mp = $mountpoint->{mp};
2098
2099 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2100
2101 return if !$storage;
2102
2103 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2104 my ($storeid, $size_gb) = ($1, $2);
2105
2106 my $size_kb = int(${size_gb}*1024) * 1024;
2107
2108 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2109 # fixme: use better naming ct-$vmid-disk-X.raw?
2110
2111 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
2112 if ($size_kb > 0) {
2113 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
2114 undef, $size_kb);
2115 format_disk($storecfg, $volid);
2116 } else {
2117 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2118 undef, 0);
2119 }
2120 } elsif ($scfg->{type} eq 'zfspool') {
2121
2122 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2123 undef, $size_kb);
2124 } elsif ($scfg->{type} eq 'drbd') {
2125
2126 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2127 format_disk($storecfg, $volid);
2128
2129 } elsif ($scfg->{type} eq 'rbd') {
2130
2131 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
2132 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2133 format_disk($storecfg, $volid);
2134 } else {
2135 die "unable to create containers on storage type '$scfg->{type}'\n";
2136 }
2137 push @$vollist, $volid;
2138 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
2139 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
2140 } else {
2141 # use specified/existing volid
2142 }
2143 });
2144 };
2145 # free allocated images on error
2146 if (my $err = $@) {
2147 destroy_disks($storecfg, $vollist);
2148 die $err;
2149 }
2150 return $vollist;
2151 }
2152
2153 # bash completion helper
2154
2155 sub complete_os_templates {
2156 my ($cmdname, $pname, $cvalue) = @_;
2157
2158 my $cfg = PVE::Storage::config();
2159
2160 my $storeid;
2161
2162 if ($cvalue =~ m/^([^:]+):/) {
2163 $storeid = $1;
2164 }
2165
2166 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2167 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2168
2169 my $res = [];
2170 foreach my $id (keys %$data) {
2171 foreach my $item (@{$data->{$id}}) {
2172 push @$res, $item->{volid} if defined($item->{volid});
2173 }
2174 }
2175
2176 return $res;
2177 }
2178
2179 my $complete_ctid_full = sub {
2180 my ($running) = @_;
2181
2182 my $idlist = vmstatus();
2183
2184 my $active_hash = list_active_containers();
2185
2186 my $res = [];
2187
2188 foreach my $id (keys %$idlist) {
2189 my $d = $idlist->{$id};
2190 if (defined($running)) {
2191 next if $d->{template};
2192 next if $running && !$active_hash->{$id};
2193 next if !$running && $active_hash->{$id};
2194 }
2195 push @$res, $id;
2196
2197 }
2198 return $res;
2199 };
2200
2201 sub complete_ctid {
2202 return &$complete_ctid_full();
2203 }
2204
2205 sub complete_ctid_stopped {
2206 return &$complete_ctid_full(0);
2207 }
2208
2209 sub complete_ctid_running {
2210 return &$complete_ctid_full(1);
2211 }
2212
2213 1;