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