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