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