]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
use sanitize_mountpoint in foreach_mountpoint
[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 # The container might have *different* symlinks than the host. realpath/abs_path
1822 # use the actual filesystem to resolve links.
1823 sub sanitize_mountpoint {
1824 my ($mp) = @_;
1825 $mp = '/' . $mp; # we always start with a slash
1826 $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
1827 $mp =~ s@/\./@@g; # collapse /./
1828 $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
1829 $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1830 $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1831 return $mp;
1832 }
1833
1834 sub foreach_mountpoint_full {
1835 my ($conf, $reverse, $func) = @_;
1836
1837 foreach my $key (mountpoint_names($reverse)) {
1838 my $value = $conf->{$key};
1839 next if !defined($value);
1840 my $mountpoint = parse_ct_mountpoint($value);
1841
1842 # just to be sure: rootfs is /
1843 my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
1844 $mountpoint->{mp} = sanitize_mountpoint($path);
1845
1846 $path = $mountpoint->{volume};
1847 $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
1848
1849 &$func($key, $mountpoint);
1850 }
1851 }
1852
1853 sub foreach_mountpoint {
1854 my ($conf, $func) = @_;
1855
1856 foreach_mountpoint_full($conf, 0, $func);
1857 }
1858
1859 sub foreach_mountpoint_reverse {
1860 my ($conf, $func) = @_;
1861
1862 foreach_mountpoint_full($conf, 1, $func);
1863 }
1864
1865 sub check_ct_modify_config_perm {
1866 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1867
1868 return 1 if $authuser ne 'root@pam';
1869
1870 foreach my $opt (@$key_list) {
1871
1872 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1873 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1874 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1875 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1876 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1877 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1878 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1879 $opt eq 'searchdomain' || $opt eq 'hostname') {
1880 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1881 } else {
1882 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1883 }
1884 }
1885
1886 return 1;
1887 }
1888
1889 sub umount_all {
1890 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1891
1892 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1893 my $volid_list = get_vm_volumes($conf);
1894
1895 foreach_mountpoint_reverse($conf, sub {
1896 my ($ms, $mountpoint) = @_;
1897
1898 my $volid = $mountpoint->{volume};
1899 my $mount = $mountpoint->{mp};
1900
1901 return if !$volid || !$mount;
1902
1903 my $mount_path = "$rootdir/$mount";
1904 $mount_path =~ s!/+!/!g;
1905
1906 return if !PVE::ProcFSTools::is_mounted($mount_path);
1907
1908 eval {
1909 PVE::Tools::run_command(['umount', '-d', $mount_path]);
1910 };
1911 if (my $err = $@) {
1912 if ($noerr) {
1913 warn $err;
1914 } else {
1915 die $err;
1916 }
1917 }
1918 });
1919 }
1920
1921 sub mount_all {
1922 my ($vmid, $storage_cfg, $conf) = @_;
1923
1924 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1925 File::Path::make_path($rootdir);
1926
1927 my $volid_list = get_vm_volumes($conf);
1928 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
1929
1930 eval {
1931 foreach_mountpoint($conf, sub {
1932 my ($ms, $mountpoint) = @_;
1933
1934 my $volid = $mountpoint->{volume};
1935 my $mount = $mountpoint->{mp};
1936
1937 return if !$volid || !$mount;
1938
1939 my $image_path = PVE::Storage::path($storage_cfg, $volid);
1940 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1941 PVE::Storage::parse_volname($storage_cfg, $volid);
1942
1943 die "unable to mount base volume - internal error" if $isBase;
1944
1945 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
1946 });
1947 };
1948 if (my $err = $@) {
1949 warn "mounting container failed - $err";
1950 umount_all($vmid, $storage_cfg, $conf, 1);
1951 }
1952
1953 return $rootdir;
1954 }
1955
1956
1957 sub mountpoint_mount_path {
1958 my ($mountpoint, $storage_cfg, $snapname) = @_;
1959
1960 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
1961 }
1962
1963 # use $rootdir = undef to just return the corresponding mount path
1964 sub mountpoint_mount {
1965 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1966
1967 my $volid = $mountpoint->{volume};
1968 my $mount = $mountpoint->{mp};
1969
1970 return if !$volid || !$mount;
1971
1972 my $mount_path;
1973
1974 if (defined($rootdir)) {
1975 $rootdir =~ s!/+$!!;
1976 $mount_path = "$rootdir/$mount";
1977 $mount_path =~ s!/+!/!g;
1978 File::Path::mkpath($mount_path);
1979 }
1980
1981 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1982
1983 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1984
1985 if ($storage) {
1986
1987 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1988 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
1989
1990 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1991 PVE::Storage::parse_volname($storage_cfg, $volid);
1992
1993 if ($format eq 'subvol') {
1994 if ($mount_path) {
1995 if ($snapname) {
1996 if ($scfg->{type} eq 'zfspool') {
1997 my $path_arg = $path;
1998 $path_arg =~ s!^/+!!;
1999 PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2000 } else {
2001 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2002 }
2003 } else {
2004 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
2005 }
2006 }
2007 return wantarray ? ($path, 0) : $path;
2008 } elsif ($format eq 'raw') {
2009 my $use_loopdev = 0;
2010 my @extra_opts;
2011 if ($scfg->{path}) {
2012 push @extra_opts, '-o', 'loop';
2013 $use_loopdev = 1;
2014 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
2015 # do nothing
2016 } else {
2017 die "unsupported storage type '$scfg->{type}'\n";
2018 }
2019 if ($mount_path) {
2020 if ($isBase || defined($snapname)) {
2021 PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2022 } else {
2023 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2024 }
2025 }
2026 return wantarray ? ($path, $use_loopdev) : $path;
2027 } else {
2028 die "unsupported image format '$format'\n";
2029 }
2030 } elsif ($volid =~ m|^/dev/.+|) {
2031 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
2032 return wantarray ? ($volid, 0) : $volid;
2033 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2034 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2035 return wantarray ? ($volid, 0) : $volid;
2036 }
2037
2038 die "unsupported storage";
2039 }
2040
2041 sub get_vm_volumes {
2042 my ($conf, $excludes) = @_;
2043
2044 my $vollist = [];
2045
2046 foreach_mountpoint($conf, sub {
2047 my ($ms, $mountpoint) = @_;
2048
2049 return if $excludes && $ms eq $excludes;
2050
2051 my $volid = $mountpoint->{volume};
2052
2053 return if !$volid || $volid =~ m|^/|;
2054
2055 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2056 return if !$sid;
2057
2058 push @$vollist, $volid;
2059 });
2060
2061 return $vollist;
2062 }
2063
2064 sub mkfs {
2065 my ($dev) = @_;
2066
2067 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2068 }
2069
2070 sub format_disk {
2071 my ($storage_cfg, $volid) = @_;
2072
2073 if ($volid =~ m!^/dev/.+!) {
2074 mkfs($volid);
2075 return;
2076 }
2077
2078 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2079
2080 die "cannot format volume '$volid' with no storage\n" if !$storage;
2081
2082 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2083
2084 my $path = PVE::Storage::path($storage_cfg, $volid);
2085
2086 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2087 PVE::Storage::parse_volname($storage_cfg, $volid);
2088
2089 die "cannot format volume '$volid' (format == $format)\n"
2090 if $format ne 'raw';
2091
2092 mkfs($path);
2093 }
2094
2095 sub destroy_disks {
2096 my ($storecfg, $vollist) = @_;
2097
2098 foreach my $volid (@$vollist) {
2099 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2100 warn $@ if $@;
2101 }
2102 }
2103
2104 sub create_disks {
2105 my ($storecfg, $vmid, $settings, $conf) = @_;
2106
2107 my $vollist = [];
2108
2109 eval {
2110 foreach_mountpoint($settings, sub {
2111 my ($ms, $mountpoint) = @_;
2112
2113 my $volid = $mountpoint->{volume};
2114 my $mp = $mountpoint->{mp};
2115
2116 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2117
2118 return if !$storage;
2119
2120 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2121 my ($storeid, $size_gb) = ($1, $2);
2122
2123 my $size_kb = int(${size_gb}*1024) * 1024;
2124
2125 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2126 # fixme: use better naming ct-$vmid-disk-X.raw?
2127
2128 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
2129 if ($size_kb > 0) {
2130 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
2131 undef, $size_kb);
2132 format_disk($storecfg, $volid);
2133 } else {
2134 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2135 undef, 0);
2136 }
2137 } elsif ($scfg->{type} eq 'zfspool') {
2138
2139 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2140 undef, $size_kb);
2141 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
2142
2143 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2144 format_disk($storecfg, $volid);
2145
2146 } elsif ($scfg->{type} eq 'rbd') {
2147
2148 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
2149 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2150 format_disk($storecfg, $volid);
2151 } else {
2152 die "unable to create containers on storage type '$scfg->{type}'\n";
2153 }
2154 push @$vollist, $volid;
2155 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
2156 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
2157 } else {
2158 # use specified/existing volid
2159 }
2160 });
2161 };
2162 # free allocated images on error
2163 if (my $err = $@) {
2164 destroy_disks($storecfg, $vollist);
2165 die $err;
2166 }
2167 return $vollist;
2168 }
2169
2170 # bash completion helper
2171
2172 sub complete_os_templates {
2173 my ($cmdname, $pname, $cvalue) = @_;
2174
2175 my $cfg = PVE::Storage::config();
2176
2177 my $storeid;
2178
2179 if ($cvalue =~ m/^([^:]+):/) {
2180 $storeid = $1;
2181 }
2182
2183 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2184 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2185
2186 my $res = [];
2187 foreach my $id (keys %$data) {
2188 foreach my $item (@{$data->{$id}}) {
2189 push @$res, $item->{volid} if defined($item->{volid});
2190 }
2191 }
2192
2193 return $res;
2194 }
2195
2196 my $complete_ctid_full = sub {
2197 my ($running) = @_;
2198
2199 my $idlist = vmstatus();
2200
2201 my $active_hash = list_active_containers();
2202
2203 my $res = [];
2204
2205 foreach my $id (keys %$idlist) {
2206 my $d = $idlist->{$id};
2207 if (defined($running)) {
2208 next if $d->{template};
2209 next if $running && !$active_hash->{$id};
2210 next if !$running && $active_hash->{$id};
2211 }
2212 push @$res, $id;
2213
2214 }
2215 return $res;
2216 };
2217
2218 sub complete_ctid {
2219 return &$complete_ctid_full();
2220 }
2221
2222 sub complete_ctid_stopped {
2223 return &$complete_ctid_full(0);
2224 }
2225
2226 sub complete_ctid_running {
2227 return &$complete_ctid_full(1);
2228 }
2229
2230 1;