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