]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
don't add binds and block devices as unused volume
[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 # skip bind mounts and block devices
1169 return if $volid =~ m|^/|;
1170
1171 my $key;
1172 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1173 my $test = "unused$ind";
1174 if (my $vid = $config->{$test}) {
1175 return if $vid eq $volid; # do not add duplicates
1176 } else {
1177 $key = $test;
1178 }
1179 }
1180
1181 die "To many unused volume - please delete them first.\n" if !$key;
1182
1183 $config->{$key} = $volid;
1184
1185 return $key;
1186 }
1187
1188 sub update_pct_config {
1189 my ($vmid, $conf, $running, $param, $delete) = @_;
1190
1191 my @nohotplug;
1192
1193 my $new_disks = 0;
1194 my @deleted_volumes;
1195
1196 my $rootdir;
1197 if ($running) {
1198 my $pid = find_lxc_pid($vmid);
1199 $rootdir = "/proc/$pid/root";
1200 }
1201
1202 my $hotplug_error = sub {
1203 if ($running) {
1204 push @nohotplug, @_;
1205 return 1;
1206 } else {
1207 return 0;
1208 }
1209 };
1210
1211 if (defined($delete)) {
1212 foreach my $opt (@$delete) {
1213 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1214 die "unable to delete required option '$opt'\n";
1215 } elsif ($opt eq 'swap') {
1216 delete $conf->{$opt};
1217 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1218 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1219 delete $conf->{$opt};
1220 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1221 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1222 next if $hotplug_error->($opt);
1223 delete $conf->{$opt};
1224 } elsif ($opt =~ m/^net(\d)$/) {
1225 delete $conf->{$opt};
1226 next if !$running;
1227 my $netid = $1;
1228 PVE::Network::veth_delete("veth${vmid}i$netid");
1229 } elsif ($opt eq 'protection') {
1230 delete $conf->{$opt};
1231 } elsif ($opt =~ m/^unused(\d+)$/) {
1232 next if $hotplug_error->($opt);
1233 check_protection($conf, "can't remove CT $vmid drive '$opt'");
1234 push @deleted_volumes, $conf->{$opt};
1235 delete $conf->{$opt};
1236 } elsif ($opt =~ m/^mp(\d+)$/) {
1237 next if $hotplug_error->($opt);
1238 check_protection($conf, "can't remove CT $vmid drive '$opt'");
1239 my $mountpoint = parse_ct_mountpoint($conf->{$opt});
1240 add_unused_volume($conf, $mountpoint->{volume});
1241 delete $conf->{$opt};
1242 } else {
1243 die "implement me"
1244 }
1245 write_config($vmid, $conf) if $running;
1246 }
1247 }
1248
1249 # There's no separate swap size to configure, there's memory and "total"
1250 # memory (iow. memory+swap). This means we have to change them together.
1251 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1252 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
1253 if (defined($wanted_memory) || defined($wanted_swap)) {
1254
1255 $wanted_memory //= ($conf->{memory} || 512);
1256 $wanted_swap //= ($conf->{swap} || 0);
1257
1258 my $total = $wanted_memory + $wanted_swap;
1259 if ($running) {
1260 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1261 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1262 }
1263 $conf->{memory} = $wanted_memory;
1264 $conf->{swap} = $wanted_swap;
1265
1266 write_config($vmid, $conf) if $running;
1267 }
1268
1269 foreach my $opt (keys %$param) {
1270 my $value = $param->{$opt};
1271 if ($opt eq 'hostname') {
1272 $conf->{$opt} = $value;
1273 } elsif ($opt eq 'onboot') {
1274 $conf->{$opt} = $value ? 1 : 0;
1275 } elsif ($opt eq 'startup') {
1276 $conf->{$opt} = $value;
1277 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1278 next if $hotplug_error->($opt);
1279 $conf->{$opt} = $value;
1280 } elsif ($opt eq 'nameserver') {
1281 next if $hotplug_error->($opt);
1282 my $list = verify_nameserver_list($value);
1283 $conf->{$opt} = $list;
1284 } elsif ($opt eq 'searchdomain') {
1285 next if $hotplug_error->($opt);
1286 my $list = verify_searchdomain_list($value);
1287 $conf->{$opt} = $list;
1288 } elsif ($opt eq 'cpulimit') {
1289 next if $hotplug_error->($opt); # FIXME: hotplug
1290 $conf->{$opt} = $value;
1291 } elsif ($opt eq 'cpuunits') {
1292 $conf->{$opt} = $value;
1293 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1294 } elsif ($opt eq 'description') {
1295 $conf->{$opt} = PVE::Tools::encode_text($value);
1296 } elsif ($opt =~ m/^net(\d+)$/) {
1297 my $netid = $1;
1298 my $net = parse_lxc_network($value);
1299 if (!$running) {
1300 $conf->{$opt} = print_lxc_network($net);
1301 } else {
1302 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1303 }
1304 } elsif ($opt eq 'protection') {
1305 $conf->{$opt} = $value ? 1 : 0;
1306 } elsif ($opt =~ m/^mp(\d+)$/) {
1307 next if $hotplug_error->($opt);
1308 check_protection($conf, "can't update CT $vmid drive '$opt'");
1309 $conf->{$opt} = $value;
1310 $new_disks = 1;
1311 } elsif ($opt eq 'rootfs') {
1312 check_protection($conf, "can't update CT $vmid drive '$opt'");
1313 die "implement me: $opt";
1314 } else {
1315 die "implement me: $opt";
1316 }
1317 write_config($vmid, $conf) if $running;
1318 }
1319
1320 if (@deleted_volumes) {
1321 my $storage_cfg = PVE::Storage::config();
1322 foreach my $volume (@deleted_volumes) {
1323 delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1324 }
1325 }
1326
1327 if ($new_disks) {
1328 my $storage_cfg = PVE::Storage::config();
1329 create_disks($storage_cfg, $vmid, $conf, $conf);
1330 }
1331
1332 # This should be the last thing we do here
1333 if ($running && scalar(@nohotplug)) {
1334 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1335 }
1336 }
1337
1338 sub has_dev_console {
1339 my ($conf) = @_;
1340
1341 return !(defined($conf->{console}) && !$conf->{console});
1342 }
1343
1344 sub get_tty_count {
1345 my ($conf) = @_;
1346
1347 return $conf->{tty} // $confdesc->{tty}->{default};
1348 }
1349
1350 sub get_cmode {
1351 my ($conf) = @_;
1352
1353 return $conf->{cmode} // $confdesc->{cmode}->{default};
1354 }
1355
1356 sub get_console_command {
1357 my ($vmid, $conf) = @_;
1358
1359 my $cmode = get_cmode($conf);
1360
1361 if ($cmode eq 'console') {
1362 return ['lxc-console', '-n', $vmid, '-t', 0];
1363 } elsif ($cmode eq 'tty') {
1364 return ['lxc-console', '-n', $vmid];
1365 } elsif ($cmode eq 'shell') {
1366 return ['lxc-attach', '--clear-env', '-n', $vmid];
1367 } else {
1368 die "internal error";
1369 }
1370 }
1371
1372 sub get_primary_ips {
1373 my ($conf) = @_;
1374
1375 # return data from net0
1376
1377 return undef if !defined($conf->{net0});
1378 my $net = parse_lxc_network($conf->{net0});
1379
1380 my $ipv4 = $net->{ip};
1381 if ($ipv4) {
1382 if ($ipv4 =~ /^(dhcp|manual)$/) {
1383 $ipv4 = undef
1384 } else {
1385 $ipv4 =~ s!/\d+$!!;
1386 }
1387 }
1388 my $ipv6 = $net->{ip6};
1389 if ($ipv6) {
1390 if ($ipv6 =~ /^(dhcp|manual)$/) {
1391 $ipv6 = undef;
1392 } else {
1393 $ipv6 =~ s!/\d+$!!;
1394 }
1395 }
1396
1397 return ($ipv4, $ipv6);
1398 }
1399
1400 sub delete_mountpoint_volume {
1401 my ($storage_cfg, $vmid, $volume) = @_;
1402
1403 # skip bind mounts and block devices
1404 if ($volume =~ m|^/|) {
1405 return;
1406 }
1407
1408 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
1409 PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
1410 }
1411
1412 sub destroy_lxc_container {
1413 my ($storage_cfg, $vmid, $conf) = @_;
1414
1415 foreach_mountpoint($conf, sub {
1416 my ($ms, $mountpoint) = @_;
1417 delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
1418 });
1419
1420 rmdir "/var/lib/lxc/$vmid/rootfs";
1421 unlink "/var/lib/lxc/$vmid/config";
1422 rmdir "/var/lib/lxc/$vmid";
1423 destroy_config($vmid);
1424
1425 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1426 #PVE::Tools::run_command($cmd);
1427 }
1428
1429 sub vm_stop_cleanup {
1430 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1431
1432 eval {
1433 if (!$keepActive) {
1434
1435 my $vollist = get_vm_volumes($conf);
1436 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
1437 }
1438 };
1439 warn $@ if $@; # avoid errors - just warn
1440 }
1441
1442 my $safe_num_ne = sub {
1443 my ($a, $b) = @_;
1444
1445 return 0 if !defined($a) && !defined($b);
1446 return 1 if !defined($a);
1447 return 1 if !defined($b);
1448
1449 return $a != $b;
1450 };
1451
1452 my $safe_string_ne = sub {
1453 my ($a, $b) = @_;
1454
1455 return 0 if !defined($a) && !defined($b);
1456 return 1 if !defined($a);
1457 return 1 if !defined($b);
1458
1459 return $a ne $b;
1460 };
1461
1462 sub update_net {
1463 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1464
1465 if ($newnet->{type} ne 'veth') {
1466 # for when there are physical interfaces
1467 die "cannot update interface of type $newnet->{type}";
1468 }
1469
1470 my $veth = "veth${vmid}i${netid}";
1471 my $eth = $newnet->{name};
1472
1473 if (my $oldnetcfg = $conf->{$opt}) {
1474 my $oldnet = parse_lxc_network($oldnetcfg);
1475
1476 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1477 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
1478
1479 PVE::Network::veth_delete($veth);
1480 delete $conf->{$opt};
1481 write_config($vmid, $conf);
1482
1483 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1484
1485 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1486 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1487 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
1488
1489 if ($oldnet->{bridge}) {
1490 PVE::Network::tap_unplug($veth);
1491 foreach (qw(bridge tag firewall)) {
1492 delete $oldnet->{$_};
1493 }
1494 $conf->{$opt} = print_lxc_network($oldnet);
1495 write_config($vmid, $conf);
1496 }
1497
1498 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1499 foreach (qw(bridge tag firewall)) {
1500 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1501 }
1502 $conf->{$opt} = print_lxc_network($oldnet);
1503 write_config($vmid, $conf);
1504 }
1505 } else {
1506 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
1507 }
1508
1509 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1510 }
1511
1512 sub hotplug_net {
1513 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1514
1515 my $veth = "veth${vmid}i${netid}";
1516 my $vethpeer = $veth . "p";
1517 my $eth = $newnet->{name};
1518
1519 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1520 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1521
1522 # attach peer in container
1523 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1524 PVE::Tools::run_command($cmd);
1525
1526 # link up peer in container
1527 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1528 PVE::Tools::run_command($cmd);
1529
1530 my $done = { type => 'veth' };
1531 foreach (qw(bridge tag firewall hwaddr name)) {
1532 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1533 }
1534 $conf->{$opt} = print_lxc_network($done);
1535
1536 write_config($vmid, $conf);
1537 }
1538
1539 sub update_ipconfig {
1540 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1541
1542 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
1543
1544 my $optdata = parse_lxc_network($conf->{$opt});
1545 my $deleted = [];
1546 my $added = [];
1547 my $nscmd = sub {
1548 my $cmdargs = shift;
1549 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1550 };
1551 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1552
1553 my $change_ip_config = sub {
1554 my ($ipversion) = @_;
1555
1556 my $family_opt = "-$ipversion";
1557 my $suffix = $ipversion == 4 ? '' : $ipversion;
1558 my $gw= "gw$suffix";
1559 my $ip= "ip$suffix";
1560
1561 my $newip = $newnet->{$ip};
1562 my $newgw = $newnet->{$gw};
1563 my $oldip = $optdata->{$ip};
1564
1565 my $change_ip = &$safe_string_ne($oldip, $newip);
1566 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1567
1568 return if !$change_ip && !$change_gw;
1569
1570 # step 1: add new IP, if this fails we cancel
1571 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1572 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1573 if (my $err = $@) {
1574 warn $err;
1575 return;
1576 }
1577 }
1578
1579 # step 2: replace gateway
1580 # If this fails we delete the added IP and cancel.
1581 # If it succeeds we save the config and delete the old IP, ignoring
1582 # errors. The config is then saved.
1583 # Note: 'ip route replace' can add
1584 if ($change_gw) {
1585 if ($newgw) {
1586 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1587 if (my $err = $@) {
1588 warn $err;
1589 # the route was not replaced, the old IP is still available
1590 # rollback (delete new IP) and cancel
1591 if ($change_ip) {
1592 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1593 warn $@ if $@; # no need to die here
1594 }
1595 return;
1596 }
1597 } else {
1598 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1599 # if the route was not deleted, the guest might have deleted it manually
1600 # warn and continue
1601 warn $@ if $@;
1602 }
1603 }
1604
1605 # from this point on we save the configuration
1606 # step 3: delete old IP ignoring errors
1607 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1608 # We need to enable promote_secondaries, otherwise our newly added
1609 # address will be removed along with the old one.
1610 my $promote = 0;
1611 eval {
1612 if ($ipversion == 4) {
1613 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1614 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1615 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1616 }
1617 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1618 };
1619 warn $@ if $@; # no need to die here
1620
1621 if ($ipversion == 4) {
1622 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1623 }
1624 }
1625
1626 foreach my $property ($ip, $gw) {
1627 if ($newnet->{$property}) {
1628 $optdata->{$property} = $newnet->{$property};
1629 } else {
1630 delete $optdata->{$property};
1631 }
1632 }
1633 $conf->{$opt} = print_lxc_network($optdata);
1634 write_config($vmid, $conf);
1635 $lxc_setup->setup_network($conf);
1636 };
1637
1638 &$change_ip_config(4);
1639 &$change_ip_config(6);
1640
1641 }
1642
1643 # Internal snapshots
1644
1645 # NOTE: Snapshot create/delete involves several non-atomic
1646 # action, and can take a long time.
1647 # So we try to avoid locking the file and use 'lock' variable
1648 # inside the config file instead.
1649
1650 my $snapshot_copy_config = sub {
1651 my ($source, $dest) = @_;
1652
1653 foreach my $k (keys %$source) {
1654 next if $k eq 'snapshots';
1655 next if $k eq 'snapstate';
1656 next if $k eq 'snaptime';
1657 next if $k eq 'vmstate';
1658 next if $k eq 'lock';
1659 next if $k eq 'digest';
1660 next if $k eq 'description';
1661
1662 $dest->{$k} = $source->{$k};
1663 }
1664 };
1665
1666 my $snapshot_prepare = sub {
1667 my ($vmid, $snapname, $comment) = @_;
1668
1669 my $snap;
1670
1671 my $updatefn = sub {
1672
1673 my $conf = load_config($vmid);
1674
1675 die "you can't take a snapshot if it's a template\n"
1676 if is_template($conf);
1677
1678 check_lock($conf);
1679
1680 $conf->{lock} = 'snapshot';
1681
1682 die "snapshot name '$snapname' already used\n"
1683 if defined($conf->{snapshots}->{$snapname});
1684
1685 my $storecfg = PVE::Storage::config();
1686 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1687
1688 $snap = $conf->{snapshots}->{$snapname} = {};
1689
1690 &$snapshot_copy_config($conf, $snap);
1691
1692 $snap->{'snapstate'} = "prepare";
1693 $snap->{'snaptime'} = time();
1694 $snap->{'description'} = $comment if $comment;
1695 $conf->{snapshots}->{$snapname} = $snap;
1696
1697 write_config($vmid, $conf);
1698 };
1699
1700 lock_container($vmid, 10, $updatefn);
1701
1702 return $snap;
1703 };
1704
1705 my $snapshot_commit = sub {
1706 my ($vmid, $snapname) = @_;
1707
1708 my $updatefn = sub {
1709
1710 my $conf = load_config($vmid);
1711
1712 die "missing snapshot lock\n"
1713 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1714
1715 die "snapshot '$snapname' does not exist\n"
1716 if !defined($conf->{snapshots}->{$snapname});
1717
1718 die "wrong snapshot state\n"
1719 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1720 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
1721
1722 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1723 delete $conf->{lock};
1724 $conf->{parent} = $snapname;
1725
1726 write_config($vmid, $conf);
1727 };
1728
1729 lock_container($vmid, 10 ,$updatefn);
1730 };
1731
1732 sub has_feature {
1733 my ($feature, $conf, $storecfg, $snapname) = @_;
1734
1735 my $err;
1736
1737 foreach_mountpoint($conf, sub {
1738 my ($ms, $mountpoint) = @_;
1739
1740 return if $err; # skip further test
1741
1742 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1743
1744 # TODO: implement support for mountpoints
1745 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1746 if $ms ne 'rootfs';
1747 });
1748
1749 return $err ? 0 : 1;
1750 }
1751
1752 sub snapshot_create {
1753 my ($vmid, $snapname, $comment) = @_;
1754
1755 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1756
1757 my $conf = load_config($vmid);
1758
1759 my $running = check_running($vmid);
1760 eval {
1761 if ($running) {
1762 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
1763 PVE::Tools::run_command(['/bin/sync']);
1764 };
1765
1766 my $storecfg = PVE::Storage::config();
1767 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
1768 my $volid = $rootinfo->{volume};
1769
1770 if ($running) {
1771 PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1772 };
1773
1774 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1775 &$snapshot_commit($vmid, $snapname);
1776 };
1777 if(my $err = $@) {
1778 snapshot_delete($vmid, $snapname, 1);
1779 die "$err\n";
1780 }
1781 }
1782
1783 sub snapshot_delete {
1784 my ($vmid, $snapname, $force) = @_;
1785
1786 my $snap;
1787
1788 my $conf;
1789
1790 my $updatefn = sub {
1791
1792 $conf = load_config($vmid);
1793
1794 die "you can't delete a snapshot if vm is a template\n"
1795 if is_template($conf);
1796
1797 $snap = $conf->{snapshots}->{$snapname};
1798
1799 check_lock($conf);
1800
1801 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1802
1803 $snap->{snapstate} = 'delete';
1804
1805 write_config($vmid, $conf);
1806 };
1807
1808 lock_container($vmid, 10, $updatefn);
1809
1810 my $storecfg = PVE::Storage::config();
1811
1812 my $del_snap = sub {
1813
1814 check_lock($conf);
1815
1816 if ($conf->{parent} eq $snapname) {
1817 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1818 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
1819 } else {
1820 delete $conf->{parent};
1821 }
1822 }
1823
1824 delete $conf->{snapshots}->{$snapname};
1825
1826 write_config($vmid, $conf);
1827 };
1828
1829 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1830 my $rootinfo = parse_ct_mountpoint($rootfs);
1831 my $volid = $rootinfo->{volume};
1832
1833 eval {
1834 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1835 };
1836 my $err = $@;
1837
1838 if(!$err || ($err && $force)) {
1839 lock_container($vmid, 10, $del_snap);
1840 if ($err) {
1841 die "Can't delete snapshot: $vmid $snapname $err\n";
1842 }
1843 }
1844 }
1845
1846 sub snapshot_rollback {
1847 my ($vmid, $snapname) = @_;
1848
1849 my $storecfg = PVE::Storage::config();
1850
1851 my $conf = load_config($vmid);
1852
1853 die "you can't rollback if vm is a template\n" if is_template($conf);
1854
1855 my $snap = $conf->{snapshots}->{$snapname};
1856
1857 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1858
1859 my $rootfs = $snap->{rootfs};
1860 my $rootinfo = parse_ct_mountpoint($rootfs);
1861 my $volid = $rootinfo->{volume};
1862
1863 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
1864
1865 my $updatefn = sub {
1866
1867 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1868 if $snap->{snapstate};
1869
1870 check_lock($conf);
1871
1872 system("lxc-stop -n $vmid --kill") if check_running($vmid);
1873
1874 die "unable to rollback vm $vmid: vm is running\n"
1875 if check_running($vmid);
1876
1877 $conf->{lock} = 'rollback';
1878
1879 my $forcemachine;
1880
1881 # copy snapshot config to current config
1882
1883 my $tmp_conf = $conf;
1884 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
1885 $conf->{snapshots} = $tmp_conf->{snapshots};
1886 delete $conf->{snaptime};
1887 delete $conf->{snapname};
1888 $conf->{parent} = $snapname;
1889
1890 write_config($vmid, $conf);
1891 };
1892
1893 my $unlockfn = sub {
1894 delete $conf->{lock};
1895 write_config($vmid, $conf);
1896 };
1897
1898 lock_container($vmid, 10, $updatefn);
1899
1900 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
1901
1902 lock_container($vmid, 5, $unlockfn);
1903 }
1904
1905 sub template_create {
1906 my ($vmid, $conf) = @_;
1907
1908 my $storecfg = PVE::Storage::config();
1909
1910 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
1911 my $volid = $rootinfo->{volume};
1912
1913 die "Template feature is not available for '$volid'\n"
1914 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1915
1916 PVE::Storage::activate_volumes($storecfg, [$volid]);
1917
1918 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1919 $rootinfo->{volume} = $template_volid;
1920 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
1921
1922 write_config($vmid, $conf);
1923 }
1924
1925 sub is_template {
1926 my ($conf) = @_;
1927
1928 return 1 if defined $conf->{template} && $conf->{template} == 1;
1929 }
1930
1931 sub mountpoint_names {
1932 my ($reverse) = @_;
1933
1934 my @names = ('rootfs');
1935
1936 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1937 push @names, "mp$i";
1938 }
1939
1940 return $reverse ? reverse @names : @names;
1941 }
1942
1943 # The container might have *different* symlinks than the host. realpath/abs_path
1944 # use the actual filesystem to resolve links.
1945 sub sanitize_mountpoint {
1946 my ($mp) = @_;
1947 $mp = '/' . $mp; # we always start with a slash
1948 $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
1949 $mp =~ s@/\./@@g; # collapse /./
1950 $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
1951 $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1952 $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1953 return $mp;
1954 }
1955
1956 sub foreach_mountpoint_full {
1957 my ($conf, $reverse, $func) = @_;
1958
1959 foreach my $key (mountpoint_names($reverse)) {
1960 my $value = $conf->{$key};
1961 next if !defined($value);
1962 my $mountpoint = parse_ct_mountpoint($value);
1963
1964 # just to be sure: rootfs is /
1965 my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
1966 $mountpoint->{mp} = sanitize_mountpoint($path);
1967
1968 $path = $mountpoint->{volume};
1969 $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
1970
1971 &$func($key, $mountpoint);
1972 }
1973 }
1974
1975 sub foreach_mountpoint {
1976 my ($conf, $func) = @_;
1977
1978 foreach_mountpoint_full($conf, 0, $func);
1979 }
1980
1981 sub foreach_mountpoint_reverse {
1982 my ($conf, $func) = @_;
1983
1984 foreach_mountpoint_full($conf, 1, $func);
1985 }
1986
1987 sub check_ct_modify_config_perm {
1988 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1989
1990 return 1 if $authuser ne 'root@pam';
1991
1992 foreach my $opt (@$key_list) {
1993
1994 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1995 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1996 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1997 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1998 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1999 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2000 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2001 $opt eq 'searchdomain' || $opt eq 'hostname') {
2002 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2003 } else {
2004 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2005 }
2006 }
2007
2008 return 1;
2009 }
2010
2011 sub umount_all {
2012 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2013
2014 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2015 my $volid_list = get_vm_volumes($conf);
2016
2017 foreach_mountpoint_reverse($conf, sub {
2018 my ($ms, $mountpoint) = @_;
2019
2020 my $volid = $mountpoint->{volume};
2021 my $mount = $mountpoint->{mp};
2022
2023 return if !$volid || !$mount;
2024
2025 my $mount_path = "$rootdir/$mount";
2026 $mount_path =~ s!/+!/!g;
2027
2028 return if !PVE::ProcFSTools::is_mounted($mount_path);
2029
2030 eval {
2031 PVE::Tools::run_command(['umount', '-d', $mount_path]);
2032 };
2033 if (my $err = $@) {
2034 if ($noerr) {
2035 warn $err;
2036 } else {
2037 die $err;
2038 }
2039 }
2040 });
2041 }
2042
2043 sub mount_all {
2044 my ($vmid, $storage_cfg, $conf) = @_;
2045
2046 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2047 File::Path::make_path($rootdir);
2048
2049 my $volid_list = get_vm_volumes($conf);
2050 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
2051
2052 eval {
2053 foreach_mountpoint($conf, sub {
2054 my ($ms, $mountpoint) = @_;
2055
2056 my $volid = $mountpoint->{volume};
2057 my $mount = $mountpoint->{mp};
2058
2059 return if !$volid || !$mount;
2060
2061 my $image_path = PVE::Storage::path($storage_cfg, $volid);
2062 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2063 PVE::Storage::parse_volname($storage_cfg, $volid);
2064
2065 die "unable to mount base volume - internal error" if $isBase;
2066
2067 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
2068 });
2069 };
2070 if (my $err = $@) {
2071 warn "mounting container failed - $err";
2072 umount_all($vmid, $storage_cfg, $conf, 1);
2073 }
2074
2075 return $rootdir;
2076 }
2077
2078
2079 sub mountpoint_mount_path {
2080 my ($mountpoint, $storage_cfg, $snapname) = @_;
2081
2082 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
2083 }
2084
2085 my $check_mount_path = sub {
2086 my ($path) = @_;
2087 $path = File::Spec->canonpath($path);
2088 my $real = Cwd::realpath($path);
2089 if ($real ne $path) {
2090 die "mount path modified by symlink: $path != $real";
2091 }
2092 };
2093
2094 # use $rootdir = undef to just return the corresponding mount path
2095 sub mountpoint_mount {
2096 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2097
2098 my $volid = $mountpoint->{volume};
2099 my $mount = $mountpoint->{mp};
2100
2101 return if !$volid || !$mount;
2102
2103 my $mount_path;
2104
2105 if (defined($rootdir)) {
2106 $rootdir =~ s!/+$!!;
2107 $mount_path = "$rootdir/$mount";
2108 $mount_path =~ s!/+!/!g;
2109 &$check_mount_path($mount_path);
2110 File::Path::mkpath($mount_path);
2111 }
2112
2113 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2114
2115 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2116
2117 if ($storage) {
2118
2119 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
2120 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
2121
2122 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2123 PVE::Storage::parse_volname($storage_cfg, $volid);
2124
2125 if ($format eq 'subvol') {
2126 if ($mount_path) {
2127 if ($snapname) {
2128 if ($scfg->{type} eq 'zfspool') {
2129 my $path_arg = $path;
2130 $path_arg =~ s!^/+!!;
2131 PVE::Tools::run_command(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2132 } else {
2133 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2134 }
2135 } else {
2136 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
2137 }
2138 }
2139 return wantarray ? ($path, 0) : $path;
2140 } elsif ($format eq 'raw') {
2141 my $use_loopdev = 0;
2142 my @extra_opts;
2143 if ($scfg->{path}) {
2144 push @extra_opts, '-o', 'loop';
2145 $use_loopdev = 1;
2146 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
2147 # do nothing
2148 } else {
2149 die "unsupported storage type '$scfg->{type}'\n";
2150 }
2151 if ($mount_path) {
2152 if ($isBase || defined($snapname)) {
2153 PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2154 } else {
2155 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2156 }
2157 }
2158 return wantarray ? ($path, $use_loopdev) : $path;
2159 } else {
2160 die "unsupported image format '$format'\n";
2161 }
2162 } elsif ($volid =~ m|^/dev/.+|) {
2163 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
2164 return wantarray ? ($volid, 0) : $volid;
2165 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2166 &$check_mount_path($volid);
2167 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2168 return wantarray ? ($volid, 0) : $volid;
2169 }
2170
2171 die "unsupported storage";
2172 }
2173
2174 sub get_vm_volumes {
2175 my ($conf, $excludes) = @_;
2176
2177 my $vollist = [];
2178
2179 foreach_mountpoint($conf, sub {
2180 my ($ms, $mountpoint) = @_;
2181
2182 return if $excludes && $ms eq $excludes;
2183
2184 my $volid = $mountpoint->{volume};
2185
2186 return if !$volid || $volid =~ m|^/|;
2187
2188 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2189 return if !$sid;
2190
2191 push @$vollist, $volid;
2192 });
2193
2194 return $vollist;
2195 }
2196
2197 sub mkfs {
2198 my ($dev) = @_;
2199
2200 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2201 }
2202
2203 sub format_disk {
2204 my ($storage_cfg, $volid) = @_;
2205
2206 if ($volid =~ m!^/dev/.+!) {
2207 mkfs($volid);
2208 return;
2209 }
2210
2211 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2212
2213 die "cannot format volume '$volid' with no storage\n" if !$storage;
2214
2215 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2216
2217 my $path = PVE::Storage::path($storage_cfg, $volid);
2218
2219 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2220 PVE::Storage::parse_volname($storage_cfg, $volid);
2221
2222 die "cannot format volume '$volid' (format == $format)\n"
2223 if $format ne 'raw';
2224
2225 mkfs($path);
2226 }
2227
2228 sub destroy_disks {
2229 my ($storecfg, $vollist) = @_;
2230
2231 foreach my $volid (@$vollist) {
2232 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2233 warn $@ if $@;
2234 }
2235 }
2236
2237 sub create_disks {
2238 my ($storecfg, $vmid, $settings, $conf) = @_;
2239
2240 my $vollist = [];
2241
2242 eval {
2243 foreach_mountpoint($settings, sub {
2244 my ($ms, $mountpoint) = @_;
2245
2246 my $volid = $mountpoint->{volume};
2247 my $mp = $mountpoint->{mp};
2248
2249 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2250
2251 return if !$storage;
2252
2253 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2254 my ($storeid, $size_gb) = ($1, $2);
2255
2256 my $size_kb = int(${size_gb}*1024) * 1024;
2257
2258 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2259 # fixme: use better naming ct-$vmid-disk-X.raw?
2260
2261 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
2262 if ($size_kb > 0) {
2263 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
2264 undef, $size_kb);
2265 format_disk($storecfg, $volid);
2266 } else {
2267 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2268 undef, 0);
2269 }
2270 } elsif ($scfg->{type} eq 'zfspool') {
2271
2272 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2273 undef, $size_kb);
2274 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
2275
2276 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2277 format_disk($storecfg, $volid);
2278
2279 } elsif ($scfg->{type} eq 'rbd') {
2280
2281 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
2282 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2283 format_disk($storecfg, $volid);
2284 } else {
2285 die "unable to create containers on storage type '$scfg->{type}'\n";
2286 }
2287 push @$vollist, $volid;
2288 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
2289 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
2290 } else {
2291 # use specified/existing volid
2292 }
2293 });
2294 };
2295 # free allocated images on error
2296 if (my $err = $@) {
2297 destroy_disks($storecfg, $vollist);
2298 die $err;
2299 }
2300 return $vollist;
2301 }
2302
2303 # bash completion helper
2304
2305 sub complete_os_templates {
2306 my ($cmdname, $pname, $cvalue) = @_;
2307
2308 my $cfg = PVE::Storage::config();
2309
2310 my $storeid;
2311
2312 if ($cvalue =~ m/^([^:]+):/) {
2313 $storeid = $1;
2314 }
2315
2316 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2317 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2318
2319 my $res = [];
2320 foreach my $id (keys %$data) {
2321 foreach my $item (@{$data->{$id}}) {
2322 push @$res, $item->{volid} if defined($item->{volid});
2323 }
2324 }
2325
2326 return $res;
2327 }
2328
2329 my $complete_ctid_full = sub {
2330 my ($running) = @_;
2331
2332 my $idlist = vmstatus();
2333
2334 my $active_hash = list_active_containers();
2335
2336 my $res = [];
2337
2338 foreach my $id (keys %$idlist) {
2339 my $d = $idlist->{$id};
2340 if (defined($running)) {
2341 next if $d->{template};
2342 next if $running && !$active_hash->{$id};
2343 next if !$running && $active_hash->{$id};
2344 }
2345 push @$res, $id;
2346
2347 }
2348 return $res;
2349 };
2350
2351 sub complete_ctid {
2352 return &$complete_ctid_full();
2353 }
2354
2355 sub complete_ctid_stopped {
2356 return &$complete_ctid_full(0);
2357 }
2358
2359 sub complete_ctid_running {
2360 return &$complete_ctid_full(1);
2361 }
2362
2363 1;