]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
lxc: use new disk option names in permission check
[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 = 0;
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 $new_disks = 1;
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 }
1223 }
1224
1225 sub has_dev_console {
1226 my ($conf) = @_;
1227
1228 return !(defined($conf->{console}) && !$conf->{console});
1229 }
1230
1231 sub get_tty_count {
1232 my ($conf) = @_;
1233
1234 return $conf->{tty} // $confdesc->{tty}->{default};
1235 }
1236
1237 sub get_cmode {
1238 my ($conf) = @_;
1239
1240 return $conf->{cmode} // $confdesc->{cmode}->{default};
1241 }
1242
1243 sub get_console_command {
1244 my ($vmid, $conf) = @_;
1245
1246 my $cmode = get_cmode($conf);
1247
1248 if ($cmode eq 'console') {
1249 return ['lxc-console', '-n', $vmid, '-t', 0];
1250 } elsif ($cmode eq 'tty') {
1251 return ['lxc-console', '-n', $vmid];
1252 } elsif ($cmode eq 'shell') {
1253 return ['lxc-attach', '--clear-env', '-n', $vmid];
1254 } else {
1255 die "internal error";
1256 }
1257 }
1258
1259 sub get_primary_ips {
1260 my ($conf) = @_;
1261
1262 # return data from net0
1263
1264 return undef if !defined($conf->{net0});
1265 my $net = parse_lxc_network($conf->{net0});
1266
1267 my $ipv4 = $net->{ip};
1268 if ($ipv4) {
1269 if ($ipv4 =~ /^(dhcp|manual)$/) {
1270 $ipv4 = undef
1271 } else {
1272 $ipv4 =~ s!/\d+$!!;
1273 }
1274 }
1275 my $ipv6 = $net->{ip6};
1276 if ($ipv6) {
1277 if ($ipv6 =~ /^(dhcp|manual)$/) {
1278 $ipv6 = undef;
1279 } else {
1280 $ipv6 =~ s!/\d+$!!;
1281 }
1282 }
1283
1284 return ($ipv4, $ipv6);
1285 }
1286
1287
1288 sub destroy_lxc_container {
1289 my ($storage_cfg, $vmid, $conf) = @_;
1290
1291 foreach_mountpoint($conf, sub {
1292 my ($ms, $mountpoint) = @_;
1293 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
1294 PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
1295 });
1296
1297 rmdir "/var/lib/lxc/$vmid/rootfs";
1298 unlink "/var/lib/lxc/$vmid/config";
1299 rmdir "/var/lib/lxc/$vmid";
1300 destroy_config($vmid);
1301
1302 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1303 #PVE::Tools::run_command($cmd);
1304 }
1305
1306 sub vm_stop_cleanup {
1307 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1308
1309 eval {
1310 if (!$keepActive) {
1311
1312 my $vollist = get_vm_volumes($conf);
1313 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
1314 }
1315 };
1316 warn $@ if $@; # avoid errors - just warn
1317 }
1318
1319 my $safe_num_ne = sub {
1320 my ($a, $b) = @_;
1321
1322 return 0 if !defined($a) && !defined($b);
1323 return 1 if !defined($a);
1324 return 1 if !defined($b);
1325
1326 return $a != $b;
1327 };
1328
1329 my $safe_string_ne = sub {
1330 my ($a, $b) = @_;
1331
1332 return 0 if !defined($a) && !defined($b);
1333 return 1 if !defined($a);
1334 return 1 if !defined($b);
1335
1336 return $a ne $b;
1337 };
1338
1339 sub update_net {
1340 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1341
1342 if ($newnet->{type} ne 'veth') {
1343 # for when there are physical interfaces
1344 die "cannot update interface of type $newnet->{type}";
1345 }
1346
1347 my $veth = "veth${vmid}i${netid}";
1348 my $eth = $newnet->{name};
1349
1350 if (my $oldnetcfg = $conf->{$opt}) {
1351 my $oldnet = parse_lxc_network($oldnetcfg);
1352
1353 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1354 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
1355
1356 PVE::Network::veth_delete($veth);
1357 delete $conf->{$opt};
1358 write_config($vmid, $conf);
1359
1360 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1361
1362 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1363 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1364 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
1365
1366 if ($oldnet->{bridge}) {
1367 PVE::Network::tap_unplug($veth);
1368 foreach (qw(bridge tag firewall)) {
1369 delete $oldnet->{$_};
1370 }
1371 $conf->{$opt} = print_lxc_network($oldnet);
1372 write_config($vmid, $conf);
1373 }
1374
1375 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1376 foreach (qw(bridge tag firewall)) {
1377 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1378 }
1379 $conf->{$opt} = print_lxc_network($oldnet);
1380 write_config($vmid, $conf);
1381 }
1382 } else {
1383 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1384 }
1385
1386 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1387 }
1388
1389 sub hotplug_net {
1390 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1391
1392 my $veth = "veth${vmid}i${netid}";
1393 my $vethpeer = $veth . "p";
1394 my $eth = $newnet->{name};
1395
1396 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1397 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1398
1399 # attach peer in container
1400 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1401 PVE::Tools::run_command($cmd);
1402
1403 # link up peer in container
1404 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1405 PVE::Tools::run_command($cmd);
1406
1407 my $done = { type => 'veth' };
1408 foreach (qw(bridge tag firewall hwaddr name)) {
1409 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1410 }
1411 $conf->{$opt} = print_lxc_network($done);
1412
1413 write_config($vmid, $conf);
1414 }
1415
1416 sub update_ipconfig {
1417 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1418
1419 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
1420
1421 my $optdata = parse_lxc_network($conf->{$opt});
1422 my $deleted = [];
1423 my $added = [];
1424 my $nscmd = sub {
1425 my $cmdargs = shift;
1426 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1427 };
1428 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1429
1430 my $change_ip_config = sub {
1431 my ($ipversion) = @_;
1432
1433 my $family_opt = "-$ipversion";
1434 my $suffix = $ipversion == 4 ? '' : $ipversion;
1435 my $gw= "gw$suffix";
1436 my $ip= "ip$suffix";
1437
1438 my $newip = $newnet->{$ip};
1439 my $newgw = $newnet->{$gw};
1440 my $oldip = $optdata->{$ip};
1441
1442 my $change_ip = &$safe_string_ne($oldip, $newip);
1443 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1444
1445 return if !$change_ip && !$change_gw;
1446
1447 # step 1: add new IP, if this fails we cancel
1448 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1449 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1450 if (my $err = $@) {
1451 warn $err;
1452 return;
1453 }
1454 }
1455
1456 # step 2: replace gateway
1457 # If this fails we delete the added IP and cancel.
1458 # If it succeeds we save the config and delete the old IP, ignoring
1459 # errors. The config is then saved.
1460 # Note: 'ip route replace' can add
1461 if ($change_gw) {
1462 if ($newgw) {
1463 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1464 if (my $err = $@) {
1465 warn $err;
1466 # the route was not replaced, the old IP is still available
1467 # rollback (delete new IP) and cancel
1468 if ($change_ip) {
1469 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1470 warn $@ if $@; # no need to die here
1471 }
1472 return;
1473 }
1474 } else {
1475 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1476 # if the route was not deleted, the guest might have deleted it manually
1477 # warn and continue
1478 warn $@ if $@;
1479 }
1480 }
1481
1482 # from this point on we save the configuration
1483 # step 3: delete old IP ignoring errors
1484 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1485 # We need to enable promote_secondaries, otherwise our newly added
1486 # address will be removed along with the old one.
1487 my $promote = 0;
1488 eval {
1489 if ($ipversion == 4) {
1490 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1491 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1492 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1493 }
1494 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1495 };
1496 warn $@ if $@; # no need to die here
1497
1498 if ($ipversion == 4) {
1499 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1500 }
1501 }
1502
1503 foreach my $property ($ip, $gw) {
1504 if ($newnet->{$property}) {
1505 $optdata->{$property} = $newnet->{$property};
1506 } else {
1507 delete $optdata->{$property};
1508 }
1509 }
1510 $conf->{$opt} = print_lxc_network($optdata);
1511 write_config($vmid, $conf);
1512 $lxc_setup->setup_network($conf);
1513 };
1514
1515 &$change_ip_config(4);
1516 &$change_ip_config(6);
1517
1518 }
1519
1520 # Internal snapshots
1521
1522 # NOTE: Snapshot create/delete involves several non-atomic
1523 # action, and can take a long time.
1524 # So we try to avoid locking the file and use 'lock' variable
1525 # inside the config file instead.
1526
1527 my $snapshot_copy_config = sub {
1528 my ($source, $dest) = @_;
1529
1530 foreach my $k (keys %$source) {
1531 next if $k eq 'snapshots';
1532 next if $k eq 'snapstate';
1533 next if $k eq 'snaptime';
1534 next if $k eq 'vmstate';
1535 next if $k eq 'lock';
1536 next if $k eq 'digest';
1537 next if $k eq 'description';
1538
1539 $dest->{$k} = $source->{$k};
1540 }
1541 };
1542
1543 my $snapshot_prepare = sub {
1544 my ($vmid, $snapname, $comment) = @_;
1545
1546 my $snap;
1547
1548 my $updatefn = sub {
1549
1550 my $conf = load_config($vmid);
1551
1552 die "you can't take a snapshot if it's a template\n"
1553 if is_template($conf);
1554
1555 check_lock($conf);
1556
1557 $conf->{lock} = 'snapshot';
1558
1559 die "snapshot name '$snapname' already used\n"
1560 if defined($conf->{snapshots}->{$snapname});
1561
1562 my $storecfg = PVE::Storage::config();
1563 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1564
1565 $snap = $conf->{snapshots}->{$snapname} = {};
1566
1567 &$snapshot_copy_config($conf, $snap);
1568
1569 $snap->{'snapstate'} = "prepare";
1570 $snap->{'snaptime'} = time();
1571 $snap->{'description'} = $comment if $comment;
1572 $conf->{snapshots}->{$snapname} = $snap;
1573
1574 write_config($vmid, $conf);
1575 };
1576
1577 lock_container($vmid, 10, $updatefn);
1578
1579 return $snap;
1580 };
1581
1582 my $snapshot_commit = sub {
1583 my ($vmid, $snapname) = @_;
1584
1585 my $updatefn = sub {
1586
1587 my $conf = load_config($vmid);
1588
1589 die "missing snapshot lock\n"
1590 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1591
1592 die "snapshot '$snapname' does not exist\n"
1593 if !defined($conf->{snapshots}->{$snapname});
1594
1595 die "wrong snapshot state\n"
1596 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1597 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
1598
1599 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1600 delete $conf->{lock};
1601 $conf->{parent} = $snapname;
1602
1603 write_config($vmid, $conf);
1604 };
1605
1606 lock_container($vmid, 10 ,$updatefn);
1607 };
1608
1609 sub has_feature {
1610 my ($feature, $conf, $storecfg, $snapname) = @_;
1611
1612 my $err;
1613
1614 foreach_mountpoint($conf, sub {
1615 my ($ms, $mountpoint) = @_;
1616
1617 return if $err; # skip further test
1618
1619 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1620
1621 # TODO: implement support for mountpoints
1622 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1623 if $ms ne 'rootfs';
1624 });
1625
1626 return $err ? 0 : 1;
1627 }
1628
1629 sub snapshot_create {
1630 my ($vmid, $snapname, $comment) = @_;
1631
1632 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1633
1634 my $conf = load_config($vmid);
1635
1636 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1637 my $running = check_running($vmid);
1638 eval {
1639 if ($running) {
1640 PVE::Tools::run_command($cmd);
1641 };
1642
1643 my $storecfg = PVE::Storage::config();
1644 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
1645 my $volid = $rootinfo->{volume};
1646
1647 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1648 if ($running) {
1649 PVE::Tools::run_command($cmd);
1650 };
1651
1652 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1653 &$snapshot_commit($vmid, $snapname);
1654 };
1655 if(my $err = $@) {
1656 snapshot_delete($vmid, $snapname, 1);
1657 die "$err\n";
1658 }
1659 }
1660
1661 sub snapshot_delete {
1662 my ($vmid, $snapname, $force) = @_;
1663
1664 my $snap;
1665
1666 my $conf;
1667
1668 my $updatefn = sub {
1669
1670 $conf = load_config($vmid);
1671
1672 die "you can't delete a snapshot if vm is a template\n"
1673 if is_template($conf);
1674
1675 $snap = $conf->{snapshots}->{$snapname};
1676
1677 check_lock($conf);
1678
1679 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1680
1681 $snap->{snapstate} = 'delete';
1682
1683 write_config($vmid, $conf);
1684 };
1685
1686 lock_container($vmid, 10, $updatefn);
1687
1688 my $storecfg = PVE::Storage::config();
1689
1690 my $del_snap = sub {
1691
1692 check_lock($conf);
1693
1694 if ($conf->{parent} eq $snapname) {
1695 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1696 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
1697 } else {
1698 delete $conf->{parent};
1699 }
1700 }
1701
1702 delete $conf->{snapshots}->{$snapname};
1703
1704 write_config($vmid, $conf);
1705 };
1706
1707 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1708 my $rootinfo = parse_ct_mountpoint($rootfs);
1709 my $volid = $rootinfo->{volume};
1710
1711 eval {
1712 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1713 };
1714 my $err = $@;
1715
1716 if(!$err || ($err && $force)) {
1717 lock_container($vmid, 10, $del_snap);
1718 if ($err) {
1719 die "Can't delete snapshot: $vmid $snapname $err\n";
1720 }
1721 }
1722 }
1723
1724 sub snapshot_rollback {
1725 my ($vmid, $snapname) = @_;
1726
1727 my $storecfg = PVE::Storage::config();
1728
1729 my $conf = load_config($vmid);
1730
1731 die "you can't rollback if vm is a template\n" if is_template($conf);
1732
1733 my $snap = $conf->{snapshots}->{$snapname};
1734
1735 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1736
1737 my $rootfs = $snap->{rootfs};
1738 my $rootinfo = parse_ct_mountpoint($rootfs);
1739 my $volid = $rootinfo->{volume};
1740
1741 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
1742
1743 my $updatefn = sub {
1744
1745 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1746 if $snap->{snapstate};
1747
1748 check_lock($conf);
1749
1750 system("lxc-stop -n $vmid --kill") if check_running($vmid);
1751
1752 die "unable to rollback vm $vmid: vm is running\n"
1753 if check_running($vmid);
1754
1755 $conf->{lock} = 'rollback';
1756
1757 my $forcemachine;
1758
1759 # copy snapshot config to current config
1760
1761 my $tmp_conf = $conf;
1762 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
1763 $conf->{snapshots} = $tmp_conf->{snapshots};
1764 delete $conf->{snaptime};
1765 delete $conf->{snapname};
1766 $conf->{parent} = $snapname;
1767
1768 write_config($vmid, $conf);
1769 };
1770
1771 my $unlockfn = sub {
1772 delete $conf->{lock};
1773 write_config($vmid, $conf);
1774 };
1775
1776 lock_container($vmid, 10, $updatefn);
1777
1778 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
1779
1780 lock_container($vmid, 5, $unlockfn);
1781 }
1782
1783 sub template_create {
1784 my ($vmid, $conf) = @_;
1785
1786 my $storecfg = PVE::Storage::config();
1787
1788 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
1789 my $volid = $rootinfo->{volume};
1790
1791 die "Template feature is not available for '$volid'\n"
1792 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1793
1794 PVE::Storage::activate_volumes($storecfg, [$volid]);
1795
1796 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1797 $rootinfo->{volume} = $template_volid;
1798 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
1799
1800 write_config($vmid, $conf);
1801 }
1802
1803 sub is_template {
1804 my ($conf) = @_;
1805
1806 return 1 if defined $conf->{template} && $conf->{template} == 1;
1807 }
1808
1809 sub mountpoint_names {
1810 my ($reverse) = @_;
1811
1812 my @names = ('rootfs');
1813
1814 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1815 push @names, "mp$i";
1816 }
1817
1818 return $reverse ? reverse @names : @names;
1819 }
1820
1821 sub foreach_mountpoint_full {
1822 my ($conf, $reverse, $func) = @_;
1823
1824 foreach my $key (mountpoint_names($reverse)) {
1825 my $value = $conf->{$key};
1826 next if !defined($value);
1827 my $mountpoint = parse_ct_mountpoint($value);
1828 $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
1829 &$func($key, $mountpoint);
1830 }
1831 }
1832
1833 sub foreach_mountpoint {
1834 my ($conf, $func) = @_;
1835
1836 foreach_mountpoint_full($conf, 0, $func);
1837 }
1838
1839 sub foreach_mountpoint_reverse {
1840 my ($conf, $func) = @_;
1841
1842 foreach_mountpoint_full($conf, 1, $func);
1843 }
1844
1845 sub check_ct_modify_config_perm {
1846 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1847
1848 return 1 if $authuser ne 'root@pam';
1849
1850 foreach my $opt (@$key_list) {
1851
1852 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1853 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1854 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1855 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1856 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1857 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1858 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1859 $opt eq 'searchdomain' || $opt eq 'hostname') {
1860 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1861 } else {
1862 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1863 }
1864 }
1865
1866 return 1;
1867 }
1868
1869 sub umount_all {
1870 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1871
1872 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1873 my $volid_list = get_vm_volumes($conf);
1874
1875 foreach_mountpoint_reverse($conf, sub {
1876 my ($ms, $mountpoint) = @_;
1877
1878 my $volid = $mountpoint->{volume};
1879 my $mount = $mountpoint->{mp};
1880
1881 return if !$volid || !$mount;
1882
1883 my $mount_path = "$rootdir/$mount";
1884 $mount_path =~ s!/+!/!g;
1885
1886 return if !PVE::ProcFSTools::is_mounted($mount_path);
1887
1888 eval {
1889 PVE::Tools::run_command(['umount', '-d', $mount_path]);
1890 };
1891 if (my $err = $@) {
1892 if ($noerr) {
1893 warn $err;
1894 } else {
1895 die $err;
1896 }
1897 }
1898 });
1899 }
1900
1901 sub mount_all {
1902 my ($vmid, $storage_cfg, $conf) = @_;
1903
1904 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1905 File::Path::make_path($rootdir);
1906
1907 my $volid_list = get_vm_volumes($conf);
1908 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
1909
1910 eval {
1911 foreach_mountpoint($conf, sub {
1912 my ($ms, $mountpoint) = @_;
1913
1914 my $volid = $mountpoint->{volume};
1915 my $mount = $mountpoint->{mp};
1916
1917 return if !$volid || !$mount;
1918
1919 my $image_path = PVE::Storage::path($storage_cfg, $volid);
1920 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1921 PVE::Storage::parse_volname($storage_cfg, $volid);
1922
1923 die "unable to mount base volume - internal error" if $isBase;
1924
1925 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
1926 });
1927 };
1928 if (my $err = $@) {
1929 warn "mounting container failed - $err";
1930 umount_all($vmid, $storage_cfg, $conf, 1);
1931 }
1932
1933 return $rootdir;
1934 }
1935
1936
1937 sub mountpoint_mount_path {
1938 my ($mountpoint, $storage_cfg, $snapname) = @_;
1939
1940 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
1941 }
1942
1943 # use $rootdir = undef to just return the corresponding mount path
1944 sub mountpoint_mount {
1945 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1946
1947 my $volid = $mountpoint->{volume};
1948 my $mount = $mountpoint->{mp};
1949
1950 return if !$volid || !$mount;
1951
1952 my $mount_path;
1953
1954 if (defined($rootdir)) {
1955 $rootdir =~ s!/+$!!;
1956 $mount_path = "$rootdir/$mount";
1957 $mount_path =~ s!/+!/!g;
1958 File::Path::mkpath($mount_path);
1959 }
1960
1961 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1962
1963 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1964
1965 if ($storage) {
1966
1967 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1968 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
1969
1970 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1971 PVE::Storage::parse_volname($storage_cfg, $volid);
1972
1973 if ($format eq 'subvol') {
1974 if ($mount_path) {
1975 if ($snapname) {
1976 if ($scfg->{type} eq 'zfspool') {
1977 my $path_arg = $path;
1978 $path_arg =~ s!^/+!!;
1979 PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
1980 } else {
1981 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1982 }
1983 } else {
1984 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
1985 }
1986 }
1987 return wantarray ? ($path, 0) : $path;
1988 } elsif ($format eq 'raw') {
1989 my $use_loopdev = 0;
1990 my @extra_opts;
1991 if ($scfg->{path}) {
1992 push @extra_opts, '-o', 'loop';
1993 $use_loopdev = 1;
1994 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
1995 # do nothing
1996 } else {
1997 die "unsupported storage type '$scfg->{type}'\n";
1998 }
1999 if ($mount_path) {
2000 if ($isBase || defined($snapname)) {
2001 PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2002 } else {
2003 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2004 }
2005 }
2006 return wantarray ? ($path, $use_loopdev) : $path;
2007 } else {
2008 die "unsupported image format '$format'\n";
2009 }
2010 } elsif ($volid =~ m|^/dev/.+|) {
2011 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
2012 return wantarray ? ($volid, 0) : $volid;
2013 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2014 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2015 return wantarray ? ($volid, 0) : $volid;
2016 }
2017
2018 die "unsupported storage";
2019 }
2020
2021 sub get_vm_volumes {
2022 my ($conf, $excludes) = @_;
2023
2024 my $vollist = [];
2025
2026 foreach_mountpoint($conf, sub {
2027 my ($ms, $mountpoint) = @_;
2028
2029 return if $excludes && $ms eq $excludes;
2030
2031 my $volid = $mountpoint->{volume};
2032
2033 return if !$volid || $volid =~ m|^/|;
2034
2035 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2036 return if !$sid;
2037
2038 push @$vollist, $volid;
2039 });
2040
2041 return $vollist;
2042 }
2043
2044 sub mkfs {
2045 my ($dev) = @_;
2046
2047 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2048 }
2049
2050 sub format_disk {
2051 my ($storage_cfg, $volid) = @_;
2052
2053 if ($volid =~ m!^/dev/.+!) {
2054 mkfs($volid);
2055 return;
2056 }
2057
2058 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2059
2060 die "cannot format volume '$volid' with no storage\n" if !$storage;
2061
2062 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2063
2064 my $path = PVE::Storage::path($storage_cfg, $volid);
2065
2066 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2067 PVE::Storage::parse_volname($storage_cfg, $volid);
2068
2069 die "cannot format volume '$volid' (format == $format)\n"
2070 if $format ne 'raw';
2071
2072 mkfs($path);
2073 }
2074
2075 sub destroy_disks {
2076 my ($storecfg, $vollist) = @_;
2077
2078 foreach my $volid (@$vollist) {
2079 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2080 warn $@ if $@;
2081 }
2082 }
2083
2084 sub create_disks {
2085 my ($storecfg, $vmid, $settings, $conf) = @_;
2086
2087 my $vollist = [];
2088
2089 eval {
2090 foreach_mountpoint($settings, sub {
2091 my ($ms, $mountpoint) = @_;
2092
2093 my $volid = $mountpoint->{volume};
2094 my $mp = $mountpoint->{mp};
2095
2096 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2097
2098 return if !$storage;
2099
2100 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2101 my ($storeid, $size_gb) = ($1, $2);
2102
2103 my $size_kb = int(${size_gb}*1024) * 1024;
2104
2105 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2106 # fixme: use better naming ct-$vmid-disk-X.raw?
2107
2108 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
2109 if ($size_kb > 0) {
2110 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
2111 undef, $size_kb);
2112 format_disk($storecfg, $volid);
2113 } else {
2114 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2115 undef, 0);
2116 }
2117 } elsif ($scfg->{type} eq 'zfspool') {
2118
2119 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2120 undef, $size_kb);
2121 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
2122
2123 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2124 format_disk($storecfg, $volid);
2125
2126 } elsif ($scfg->{type} eq 'rbd') {
2127
2128 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
2129 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2130 format_disk($storecfg, $volid);
2131 } else {
2132 die "unable to create containers on storage type '$scfg->{type}'\n";
2133 }
2134 push @$vollist, $volid;
2135 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
2136 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
2137 } else {
2138 # use specified/existing volid
2139 }
2140 });
2141 };
2142 # free allocated images on error
2143 if (my $err = $@) {
2144 destroy_disks($storecfg, $vollist);
2145 die $err;
2146 }
2147 return $vollist;
2148 }
2149
2150 # bash completion helper
2151
2152 sub complete_os_templates {
2153 my ($cmdname, $pname, $cvalue) = @_;
2154
2155 my $cfg = PVE::Storage::config();
2156
2157 my $storeid;
2158
2159 if ($cvalue =~ m/^([^:]+):/) {
2160 $storeid = $1;
2161 }
2162
2163 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2164 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2165
2166 my $res = [];
2167 foreach my $id (keys %$data) {
2168 foreach my $item (@{$data->{$id}}) {
2169 push @$res, $item->{volid} if defined($item->{volid});
2170 }
2171 }
2172
2173 return $res;
2174 }
2175
2176 my $complete_ctid_full = sub {
2177 my ($running) = @_;
2178
2179 my $idlist = vmstatus();
2180
2181 my $active_hash = list_active_containers();
2182
2183 my $res = [];
2184
2185 foreach my $id (keys %$idlist) {
2186 my $d = $idlist->{$id};
2187 if (defined($running)) {
2188 next if $d->{template};
2189 next if $running && !$active_hash->{$id};
2190 next if !$running && $active_hash->{$id};
2191 }
2192 push @$res, $id;
2193
2194 }
2195 return $res;
2196 };
2197
2198 sub complete_ctid {
2199 return &$complete_ctid_full();
2200 }
2201
2202 sub complete_ctid_stopped {
2203 return &$complete_ctid_full(0);
2204 }
2205
2206 sub complete_ctid_running {
2207 return &$complete_ctid_full(1);
2208 }
2209
2210 1;