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