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