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