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