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