]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
fixup: slight code cleanup
[pve-container.git] / src / PVE / LXC / Config.pm
1 package PVE::LXC::Config;
2
3 use strict;
4 use warnings;
5
6 use PVE::AbstractConfig;
7 use PVE::Cluster qw(cfs_register_file);
8 use PVE::INotify;
9 use PVE::JSONSchema qw(get_standard_option);
10 use PVE::Tools;
11
12 use base qw(PVE::AbstractConfig);
13
14 my $nodename = PVE::INotify::nodename();
15 my $lock_handles = {};
16 my $lockdir = "/run/lock/lxc";
17 mkdir $lockdir;
18 mkdir "/etc/pve/nodes/$nodename/lxc";
19 my $MAX_MOUNT_POINTS = 256;
20 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
21
22 # BEGIN implemented abstract methods from PVE::AbstractConfig
23
24 sub guest_type {
25 return "CT";
26 }
27
28 sub __config_max_unused_disks {
29 my ($class) = @_;
30
31 return $MAX_UNUSED_DISKS;
32 }
33
34 sub config_file_lock {
35 my ($class, $vmid) = @_;
36
37 return "$lockdir/pve-config-${vmid}.lock";
38 }
39
40 sub cfs_config_path {
41 my ($class, $vmid, $node) = @_;
42
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
45 }
46
47 sub mountpoint_backup_enabled {
48 my ($class, $mp_key, $mountpoint) = @_;
49
50 return 1 if $mp_key eq 'rootfs';
51
52 return 0 if $mountpoint->{type} ne 'volume';
53
54 return 1 if $mountpoint->{backup};
55
56 return 0;
57 }
58
59 sub has_feature {
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
61 my $err;
62
63 $class->foreach_mountpoint($conf, sub {
64 my ($ms, $mountpoint) = @_;
65
66 return if $err; # skip further test
67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
68
69 $err = 1
70 if !PVE::Storage::volume_has_feature($storecfg, $feature,
71 $mountpoint->{volume},
72 $snapname, $running);
73 });
74
75 return $err ? 0 : 1;
76 }
77
78 sub __snapshot_save_vmstate {
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
81 }
82
83 sub __snapshot_check_running {
84 my ($class, $vmid) = @_;
85 return PVE::LXC::check_running($vmid);
86 }
87
88 sub __snapshot_check_freeze_needed {
89 my ($class, $vmid, $config, $save_vmstate) = @_;
90
91 my $ret = $class->__snapshot_check_running($vmid);
92 return ($ret, $ret);
93 }
94
95 sub __snapshot_freeze {
96 my ($class, $vmid, $unfreeze) = @_;
97
98 if ($unfreeze) {
99 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
100 warn $@ if $@;
101 } else {
102 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
103 PVE::LXC::sync_container_namespace($vmid);
104 }
105 }
106
107 sub __snapshot_create_vol_snapshot {
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
109
110 my $storecfg = PVE::Storage::config();
111
112 return if $snapname eq 'vzdump' &&
113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
114
115 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
116 }
117
118 sub __snapshot_delete_remove_drive {
119 my ($class, $snap, $remove_drive) = @_;
120
121 if ($remove_drive eq 'vmstate') {
122 die "implement me - saving vmstate\n";
123 } else {
124 my $value = $snap->{$remove_drive};
125 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
126 delete $snap->{$remove_drive};
127
128 $class->add_unused_volume($snap, $mountpoint->{volume})
129 if ($mountpoint->{type} eq 'volume');
130 }
131 }
132
133 sub __snapshot_delete_vmstate_file {
134 my ($class, $snap, $force) = @_;
135
136 die "implement me - saving vmstate\n";
137 }
138
139 sub __snapshot_delete_vol_snapshot {
140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
141
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
144
145 my $storecfg = PVE::Storage::config();
146 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
147 push @$unused, $mountpoint->{volume};
148 }
149
150 sub __snapshot_rollback_vol_possible {
151 my ($class, $mountpoint, $snapname) = @_;
152
153 my $storecfg = PVE::Storage::config();
154 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
155 }
156
157 sub __snapshot_rollback_vol_rollback {
158 my ($class, $mountpoint, $snapname) = @_;
159
160 my $storecfg = PVE::Storage::config();
161 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
162 }
163
164 sub __snapshot_rollback_vm_stop {
165 my ($class, $vmid) = @_;
166
167 PVE::LXC::vm_stop($vmid, 1)
168 if $class->__snapshot_check_running($vmid);
169 }
170
171 sub __snapshot_rollback_vm_start {
172 my ($class, $vmid, $vmstate, $data);
173
174 die "implement me - save vmstate\n";
175 }
176
177 sub __snapshot_rollback_get_unused {
178 my ($class, $conf, $snap) = @_;
179
180 my $unused = [];
181
182 $class->__snapshot_foreach_volume($conf, sub {
183 my ($vs, $volume) = @_;
184
185 return if $volume->{type} ne 'volume';
186
187 my $found = 0;
188 my $volid = $volume->{volume};
189
190 $class->__snapshot_foreach_volume($snap, sub {
191 my ($ms, $mountpoint) = @_;
192
193 return if $found;
194 return if ($mountpoint->{type} ne 'volume');
195
196 $found = 1
197 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
198 });
199
200 push @$unused, $volid if !$found;
201 });
202
203 return $unused;
204 }
205
206 sub __snapshot_foreach_volume {
207 my ($class, $conf, $func) = @_;
208
209 $class->foreach_mountpoint($conf, $func);
210 }
211
212 # END implemented abstract methods from PVE::AbstractConfig
213
214 # BEGIN JSON config code
215
216 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
217
218 my $rootfs_desc = {
219 volume => {
220 type => 'string',
221 default_key => 1,
222 format => 'pve-lxc-mp-string',
223 format_description => 'volume',
224 description => 'Volume, device or directory to mount into the container.',
225 },
226 size => {
227 type => 'string',
228 format => 'disk-size',
229 format_description => 'DiskSize',
230 description => 'Volume size (read only value).',
231 optional => 1,
232 },
233 acl => {
234 type => 'boolean',
235 description => 'Explicitly enable or disable ACL support.',
236 optional => 1,
237 },
238 ro => {
239 type => 'boolean',
240 description => 'Read-only mount point',
241 optional => 1,
242 },
243 quota => {
244 type => 'boolean',
245 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
246 optional => 1,
247 },
248 replicate => {
249 type => 'boolean',
250 description => 'Will include this volume to a storage replica job.',
251 optional => 1,
252 default => 1,
253 },
254 shared => {
255 type => 'boolean',
256 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
257 verbose_description => "Mark this non-volume mount point as available on all nodes.\n\nWARNING: This option does not share the mount point automatically, it assumes it is shared already!",
258 optional => 1,
259 default => 0,
260 },
261 };
262
263 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
264 type => 'string', format => $rootfs_desc,
265 description => "Use volume as container root.",
266 optional => 1,
267 });
268
269 PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
270 description => "The name of the snapshot.",
271 type => 'string', format => 'pve-configid',
272 maxLength => 40,
273 });
274
275 my $features_desc = {
276 mount => {
277 optional => 1,
278 type => 'string',
279 description => "Allow mounting file systems of specific types."
280 ." This should be a list of file system types as used with the mount command."
281 ." Note that this can have negative effects on the container's security."
282 ." With access to a loop device, mounting a file can circumvent the mknod"
283 ." permission of the devices cgroup, mounting an NFS file system can"
284 ." block the host's I/O completely and prevent it from rebooting, etc.",
285 format_description => 'fstype;fstype;...',
286 pattern => qr/[a-zA-Z0-9; ]+/,
287 },
288 nesting => {
289 optional => 1,
290 type => 'boolean',
291 default => 0,
292 description => "Allow nesting."
293 ." Best used with unprivileged containers with additional id mapping."
294 ." Note that this will expose procfs and sysfs contents of the host"
295 ." to the guest.",
296 },
297 keyctl => {
298 optional => 1,
299 type => 'boolean',
300 default => 0,
301 description => "For unprivileged containers only: Allow the use of the keyctl() system call."
302 ." This is required to use docker inside a container."
303 ." By default unprivileged containers will see this system call as non-existent."
304 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
305 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
306 ." Essentially, you can choose between running systemd-networkd or docker.",
307 },
308 fuse => {
309 optional => 1,
310 type => 'boolean',
311 default => 0,
312 description => "Allow using 'fuse' file systems in a container."
313 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
314 },
315 };
316
317 my $confdesc = {
318 lock => {
319 optional => 1,
320 type => 'string',
321 description => "Lock/unlock the VM.",
322 enum => [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
323 },
324 onboot => {
325 optional => 1,
326 type => 'boolean',
327 description => "Specifies whether a VM will be started during system bootup.",
328 default => 0,
329 },
330 startup => get_standard_option('pve-startup-order'),
331 template => {
332 optional => 1,
333 type => 'boolean',
334 description => "Enable/disable Template.",
335 default => 0,
336 },
337 arch => {
338 optional => 1,
339 type => 'string',
340 enum => ['amd64', 'i386', 'arm64', 'armhf'],
341 description => "OS architecture type.",
342 default => 'amd64',
343 },
344 ostype => {
345 optional => 1,
346 type => 'string',
347 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
348 description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
349 },
350 console => {
351 optional => 1,
352 type => 'boolean',
353 description => "Attach a console device (/dev/console) to the container.",
354 default => 1,
355 },
356 tty => {
357 optional => 1,
358 type => 'integer',
359 description => "Specify the number of tty available to the container",
360 minimum => 0,
361 maximum => 6,
362 default => 2,
363 },
364 cores => {
365 optional => 1,
366 type => 'integer',
367 description => "The number of cores assigned to the container. A container can use all available cores by default.",
368 minimum => 1,
369 maximum => 128,
370 },
371 cpulimit => {
372 optional => 1,
373 type => 'number',
374 description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
375 minimum => 0,
376 maximum => 128,
377 default => 0,
378 },
379 cpuunits => {
380 optional => 1,
381 type => 'integer',
382 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 the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
383 minimum => 0,
384 maximum => 500000,
385 default => 1024,
386 },
387 memory => {
388 optional => 1,
389 type => 'integer',
390 description => "Amount of RAM for the VM in MB.",
391 minimum => 16,
392 default => 512,
393 },
394 swap => {
395 optional => 1,
396 type => 'integer',
397 description => "Amount of SWAP for the VM in MB.",
398 minimum => 0,
399 default => 512,
400 },
401 hostname => {
402 optional => 1,
403 description => "Set a host name for the container.",
404 type => 'string', format => 'dns-name',
405 maxLength => 255,
406 },
407 description => {
408 optional => 1,
409 type => 'string',
410 description => "Container description. Only used on the configuration web interface.",
411 },
412 searchdomain => {
413 optional => 1,
414 type => 'string', format => 'dns-name-list',
415 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
416 },
417 nameserver => {
418 optional => 1,
419 type => 'string', format => 'address-list',
420 description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
421 },
422 rootfs => get_standard_option('pve-ct-rootfs'),
423 parent => {
424 optional => 1,
425 type => 'string', format => 'pve-configid',
426 maxLength => 40,
427 description => "Parent snapshot name. This is used internally, and should not be modified.",
428 },
429 snaptime => {
430 optional => 1,
431 description => "Timestamp for snapshots.",
432 type => 'integer',
433 minimum => 0,
434 },
435 cmode => {
436 optional => 1,
437 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).",
438 type => 'string',
439 enum => ['shell', 'console', 'tty'],
440 default => 'tty',
441 },
442 protection => {
443 optional => 1,
444 type => 'boolean',
445 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
446 default => 0,
447 },
448 unprivileged => {
449 optional => 1,
450 type => 'boolean',
451 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
452 default => 0,
453 },
454 features => {
455 optional => 1,
456 type => 'string',
457 format => $features_desc,
458 description => "Allow containers access to advanced features.",
459 },
460 };
461
462 my $valid_lxc_conf_keys = {
463 'lxc.apparmor.profile' => 1,
464 'lxc.apparmor.allow_incomplete' => 1,
465 'lxc.apparmor.allow_nesting' => 1,
466 'lxc.apparmor.raw' => 1,
467 'lxc.selinux.context' => 1,
468 'lxc.include' => 1,
469 'lxc.arch' => 1,
470 'lxc.uts.name' => 1,
471 'lxc.signal.halt' => 1,
472 'lxc.signal.reboot' => 1,
473 'lxc.signal.stop' => 1,
474 'lxc.init.cmd' => 1,
475 'lxc.pty.max' => 1,
476 'lxc.console.logfile' => 1,
477 'lxc.console.path' => 1,
478 'lxc.tty.max' => 1,
479 'lxc.devtty.dir' => 1,
480 'lxc.hook.autodev' => 1,
481 'lxc.autodev' => 1,
482 'lxc.kmsg' => 1,
483 'lxc.mount.fstab' => 1,
484 'lxc.mount.entry' => 1,
485 'lxc.mount.auto' => 1,
486 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
487 'lxc.rootfs.mount' => 1,
488 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
489 ', please use mount point options in the "rootfs" key',
490 # lxc.cgroup.*
491 # lxc.prlimit.*
492 # lxc.net.*
493 'lxc.cap.drop' => 1,
494 'lxc.cap.keep' => 1,
495 'lxc.seccomp.profile' => 1,
496 'lxc.idmap' => 1,
497 'lxc.hook.pre-start' => 1,
498 'lxc.hook.pre-mount' => 1,
499 'lxc.hook.mount' => 1,
500 'lxc.hook.start' => 1,
501 'lxc.hook.stop' => 1,
502 'lxc.hook.post-stop' => 1,
503 'lxc.hook.clone' => 1,
504 'lxc.hook.destroy' => 1,
505 'lxc.log.level' => 1,
506 'lxc.log.file' => 1,
507 'lxc.start.auto' => 1,
508 'lxc.start.delay' => 1,
509 'lxc.start.order' => 1,
510 'lxc.group' => 1,
511 'lxc.environment' => 1,
512
513 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
514 'lxc.sysctl.fs.mqueue' => 1,
515 'lxc.sysctl.kernel.msgmax' => 1,
516 'lxc.sysctl.kernel.msgmnb' => 1,
517 'lxc.sysctl.kernel.msgmni' => 1,
518 'lxc.sysctl.kernel.sem' => 1,
519 'lxc.sysctl.kernel.shmall' => 1,
520 'lxc.sysctl.kernel.shmmax' => 1,
521 'lxc.sysctl.kernel.shmmni' => 1,
522 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
523 };
524
525 my $deprecated_lxc_conf_keys = {
526 # Deprecated (removed with lxc 3.0):
527 'lxc.aa_profile' => 'lxc.apparmor.profile',
528 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
529 'lxc.console' => 'lxc.console.path',
530 'lxc.devttydir' => 'lxc.tty.dir',
531 'lxc.haltsignal' => 'lxc.signal.halt',
532 'lxc.rebootsignal' => 'lxc.signal.reboot',
533 'lxc.stopsignal' => 'lxc.signal.stop',
534 'lxc.id_map' => 'lxc.idmap',
535 'lxc.init_cmd' => 'lxc.init.cmd',
536 'lxc.loglevel' => 'lxc.log.level',
537 'lxc.logfile' => 'lxc.log.file',
538 'lxc.mount' => 'lxc.mount.fstab',
539 'lxc.network.type' => 'lxc.net.INDEX.type',
540 'lxc.network.flags' => 'lxc.net.INDEX.flags',
541 'lxc.network.link' => 'lxc.net.INDEX.link',
542 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
543 'lxc.network.name' => 'lxc.net.INDEX.name',
544 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
545 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
546 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
547 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
548 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
549 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
550 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
551 'lxc.pts' => 'lxc.pty.max',
552 'lxc.se_context' => 'lxc.selinux.context',
553 'lxc.seccomp' => 'lxc.seccomp.profile',
554 'lxc.tty' => 'lxc.tty.max',
555 'lxc.utsname' => 'lxc.uts.name',
556 };
557
558 sub is_valid_lxc_conf_key {
559 my ($vmid, $key) = @_;
560 if ($key =~ /^lxc\.limit\./) {
561 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
562 return 1;
563 }
564 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
565 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
566 return 1;
567 }
568 my $validity = $valid_lxc_conf_keys->{$key};
569 return $validity if defined($validity);
570 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
571 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
572 || $key =~ /^lxc\.net\./; # allow custom network definitions
573 return 0;
574 }
575
576 our $netconf_desc = {
577 type => {
578 type => 'string',
579 optional => 1,
580 description => "Network interface type.",
581 enum => [qw(veth)],
582 },
583 name => {
584 type => 'string',
585 format_description => 'string',
586 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
587 pattern => '[-_.\w\d]+',
588 },
589 bridge => {
590 type => 'string',
591 format_description => 'bridge',
592 description => 'Bridge to attach the network device to.',
593 pattern => '[-_.\w\d]+',
594 optional => 1,
595 },
596 hwaddr => {
597 type => 'string',
598 format_description => "XX:XX:XX:XX:XX:XX",
599 description => 'The interface MAC address. This is dynamically allocated by default, but you can set that statically if needed, for example to always have the same link-local IPv6 address. (lxc.network.hwaddr)',
600 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
601 optional => 1,
602 },
603 mtu => {
604 type => 'integer',
605 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
606 minimum => 64, # minimum ethernet frame is 64 bytes
607 optional => 1,
608 },
609 ip => {
610 type => 'string',
611 format => 'pve-ipv4-config',
612 format_description => '(IPv4/CIDR|dhcp|manual)',
613 description => 'IPv4 address in CIDR format.',
614 optional => 1,
615 },
616 gw => {
617 type => 'string',
618 format => 'ipv4',
619 format_description => 'GatewayIPv4',
620 description => 'Default gateway for IPv4 traffic.',
621 optional => 1,
622 },
623 ip6 => {
624 type => 'string',
625 format => 'pve-ipv6-config',
626 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
627 description => 'IPv6 address in CIDR format.',
628 optional => 1,
629 },
630 gw6 => {
631 type => 'string',
632 format => 'ipv6',
633 format_description => 'GatewayIPv6',
634 description => 'Default gateway for IPv6 traffic.',
635 optional => 1,
636 },
637 firewall => {
638 type => 'boolean',
639 description => "Controls whether this interface's firewall rules should be used.",
640 optional => 1,
641 },
642 tag => {
643 type => 'integer',
644 minimum => 1,
645 maximum => 4094,
646 description => "VLAN tag for this interface.",
647 optional => 1,
648 },
649 trunks => {
650 type => 'string',
651 pattern => qr/\d+(?:;\d+)*/,
652 format_description => 'vlanid[;vlanid...]',
653 description => "VLAN ids to pass through the interface",
654 optional => 1,
655 },
656 rate => {
657 type => 'number',
658 format_description => 'mbps',
659 description => "Apply rate limiting to the interface",
660 optional => 1,
661 },
662 };
663 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
664
665 my $MAX_LXC_NETWORKS = 10;
666 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
667 $confdesc->{"net$i"} = {
668 optional => 1,
669 type => 'string', format => $netconf_desc,
670 description => "Specifies network interfaces for the container.",
671 };
672 }
673
674 PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
675 sub verify_lxc_mp_string {
676 my ($mp, $noerr) = @_;
677
678 # do not allow:
679 # /./ or /../
680 # /. or /.. at the end
681 # ../ at the beginning
682
683 if($mp =~ m@/\.\.?/@ ||
684 $mp =~ m@/\.\.?$@ ||
685 $mp =~ m@^\.\./@) {
686 return undef if $noerr;
687 die "$mp contains illegal character sequences\n";
688 }
689 return $mp;
690 }
691
692 my $mp_desc = {
693 %$rootfs_desc,
694 backup => {
695 type => 'boolean',
696 description => 'Whether to include the mount point in backups.',
697 verbose_description => 'Whether to include the mount point in backups '.
698 '(only used for volume mount points).',
699 optional => 1,
700 },
701 mp => {
702 type => 'string',
703 format => 'pve-lxc-mp-string',
704 format_description => 'Path',
705 description => 'Path to the mount point as seen from inside the container '.
706 '(must not contain symlinks).',
707 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
708 "NOTE: Must not contain any symlinks for security reasons."
709 },
710 };
711 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
712
713 my $unuseddesc = {
714 optional => 1,
715 type => 'string', format => 'pve-volume-id',
716 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
717 };
718
719 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
720 $confdesc->{"mp$i"} = {
721 optional => 1,
722 type => 'string', format => $mp_desc,
723 description => "Use volume as container mount point.",
724 optional => 1,
725 };
726 }
727
728 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
729 $confdesc->{"unused$i"} = $unuseddesc;
730 }
731
732 sub parse_pct_config {
733 my ($filename, $raw) = @_;
734
735 return undef if !defined($raw);
736
737 my $res = {
738 digest => Digest::SHA::sha1_hex($raw),
739 snapshots => {},
740 };
741
742 $filename =~ m|/lxc/(\d+).conf$|
743 || die "got strange filename '$filename'";
744
745 my $vmid = $1;
746
747 my $conf = $res;
748 my $descr = '';
749 my $section = '';
750
751 my @lines = split(/\n/, $raw);
752 foreach my $line (@lines) {
753 next if $line =~ m/^\s*$/;
754
755 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
756 $section = $1;
757 $conf->{description} = $descr if $descr;
758 $descr = '';
759 $conf = $res->{snapshots}->{$section} = {};
760 next;
761 }
762
763 if ($line =~ m/^\#(.*)\s*$/) {
764 $descr .= PVE::Tools::decode_text($1) . "\n";
765 next;
766 }
767
768 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
769 my $key = $1;
770 my $value = $3;
771 my $validity = is_valid_lxc_conf_key($vmid, $key);
772 if ($validity eq 1) {
773 push @{$conf->{lxc}}, [$key, $value];
774 } elsif (my $errmsg = $validity) {
775 warn "vm $vmid - $key: $errmsg\n";
776 } else {
777 warn "vm $vmid - unable to parse config: $line\n";
778 }
779 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
780 $descr .= PVE::Tools::decode_text($2);
781 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
782 $conf->{snapstate} = $1;
783 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
784 my $key = $1;
785 my $value = $2;
786 eval { $value = PVE::LXC::Config->check_type($key, $value); };
787 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
788 $conf->{$key} = $value;
789 } else {
790 warn "vm $vmid - unable to parse config: $line\n";
791 }
792 }
793
794 $conf->{description} = $descr if $descr;
795
796 delete $res->{snapstate}; # just to be sure
797
798 return $res;
799 }
800
801 sub write_pct_config {
802 my ($filename, $conf) = @_;
803
804 delete $conf->{snapstate}; # just to be sure
805
806 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
807 my $used_volids = {};
808 foreach my $vid (@$volidlist) {
809 $used_volids->{$vid} = 1;
810 }
811
812 # remove 'unusedX' settings if the volume is still used
813 foreach my $key (keys %$conf) {
814 my $value = $conf->{$key};
815 if ($key =~ m/^unused/ && $used_volids->{$value}) {
816 delete $conf->{$key};
817 }
818 }
819
820 my $generate_raw_config = sub {
821 my ($conf) = @_;
822
823 my $raw = '';
824
825 # add description as comment to top of file
826 my $descr = $conf->{description} || '';
827 foreach my $cl (split(/\n/, $descr)) {
828 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
829 }
830
831 foreach my $key (sort keys %$conf) {
832 next if $key eq 'digest' || $key eq 'description' ||
833 $key eq 'pending' || $key eq 'snapshots' ||
834 $key eq 'snapname' || $key eq 'lxc';
835 my $value = $conf->{$key};
836 die "detected invalid newline inside property '$key'\n"
837 if $value =~ m/\n/;
838 $raw .= "$key: $value\n";
839 }
840
841 if (my $lxcconf = $conf->{lxc}) {
842 foreach my $entry (@$lxcconf) {
843 my ($k, $v) = @$entry;
844 $raw .= "$k: $v\n";
845 }
846 }
847
848 return $raw;
849 };
850
851 my $raw = &$generate_raw_config($conf);
852
853 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
854 $raw .= "\n[$snapname]\n";
855 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
856 }
857
858 return $raw;
859 }
860
861 sub update_pct_config {
862 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
863
864 my @nohotplug;
865
866 my $new_disks = 0;
867 my @deleted_volumes;
868
869 my $rootdir;
870 if ($running) {
871 my $pid = PVE::LXC::find_lxc_pid($vmid);
872 $rootdir = "/proc/$pid/root";
873 }
874
875 my $hotplug_error = sub {
876 if ($running) {
877 push @nohotplug, @_;
878 return 1;
879 } else {
880 return 0;
881 }
882 };
883
884 if (defined($delete)) {
885 foreach my $opt (@$delete) {
886 if (!exists($conf->{$opt})) {
887 # silently ignore
888 next;
889 }
890
891 if ($opt eq 'memory' || $opt eq 'rootfs') {
892 die "unable to delete required option '$opt'\n";
893 } elsif ($opt eq 'hostname') {
894 delete $conf->{$opt};
895 } elsif ($opt eq 'swap') {
896 delete $conf->{$opt};
897 PVE::LXC::write_cgroup_value("memory", $vmid,
898 "memory.memsw.limit_in_bytes", -1);
899 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
900 delete $conf->{$opt};
901 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
902 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
903 next if $hotplug_error->($opt);
904 delete $conf->{$opt};
905 } elsif ($opt eq 'cores') {
906 delete $conf->{$opt}; # rest is handled by pvestatd
907 } elsif ($opt eq 'cpulimit') {
908 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
909 delete $conf->{$opt};
910 } elsif ($opt eq 'cpuunits') {
911 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
912 delete $conf->{$opt};
913 } elsif ($opt =~ m/^net(\d)$/) {
914 delete $conf->{$opt};
915 next if !$running;
916 my $netid = $1;
917 PVE::Network::veth_delete("veth${vmid}i$netid");
918 } elsif ($opt eq 'protection') {
919 delete $conf->{$opt};
920 } elsif ($opt =~ m/^unused(\d+)$/) {
921 next if $hotplug_error->($opt);
922 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
923 push @deleted_volumes, $conf->{$opt};
924 delete $conf->{$opt};
925 } elsif ($opt =~ m/^mp(\d+)$/) {
926 next if $hotplug_error->($opt);
927 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
928 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
929 delete $conf->{$opt};
930 if ($mp->{type} eq 'volume') {
931 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
932 }
933 } elsif ($opt eq 'unprivileged') {
934 die "unable to delete read-only option: '$opt'\n";
935 } elsif ($opt eq 'features') {
936 next if $hotplug_error->($opt);
937 delete $conf->{$opt};
938 } else {
939 die "implement me (delete: $opt)"
940 }
941 PVE::LXC::Config->write_config($vmid, $conf) if $running;
942 }
943 }
944
945 # There's no separate swap size to configure, there's memory and "total"
946 # memory (iow. memory+swap). This means we have to change them together.
947 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
948 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
949 if (defined($wanted_memory) || defined($wanted_swap)) {
950
951 my $old_memory = ($conf->{memory} || 512);
952 my $old_swap = ($conf->{swap} || 0);
953
954 $wanted_memory //= $old_memory;
955 $wanted_swap //= $old_swap;
956
957 my $total = $wanted_memory + $wanted_swap;
958 if ($running) {
959 my $old_total = $old_memory + $old_swap;
960 if ($total > $old_total) {
961 PVE::LXC::write_cgroup_value("memory", $vmid,
962 "memory.memsw.limit_in_bytes",
963 int($total*1024*1024));
964 PVE::LXC::write_cgroup_value("memory", $vmid,
965 "memory.limit_in_bytes",
966 int($wanted_memory*1024*1024));
967 } else {
968 PVE::LXC::write_cgroup_value("memory", $vmid,
969 "memory.limit_in_bytes",
970 int($wanted_memory*1024*1024));
971 PVE::LXC::write_cgroup_value("memory", $vmid,
972 "memory.memsw.limit_in_bytes",
973 int($total*1024*1024));
974 }
975 }
976 $conf->{memory} = $wanted_memory;
977 $conf->{swap} = $wanted_swap;
978
979 PVE::LXC::Config->write_config($vmid, $conf) if $running;
980 }
981
982 my $storecfg = PVE::Storage::config();
983
984 my $used_volids = {};
985 my $check_content_type = sub {
986 my ($mp) = @_;
987 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
988 my $storage_config = PVE::Storage::storage_config($storecfg, $sid);
989 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
990 if !$storage_config->{content}->{rootdir};
991 };
992
993 my $rescan_volume = sub {
994 my ($mp) = @_;
995 eval {
996 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
997 if !defined($mp->{size});
998 };
999 warn "Could not rescan volume size - $@\n" if $@;
1000 };
1001
1002 foreach my $opt (keys %$param) {
1003 my $value = $param->{$opt};
1004 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
1005 if ($opt eq 'hostname' || $opt eq 'arch') {
1006 $conf->{$opt} = $value;
1007 } elsif ($opt eq 'onboot') {
1008 $conf->{$opt} = $value ? 1 : 0;
1009 } elsif ($opt eq 'startup') {
1010 $conf->{$opt} = $value;
1011 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1012 next if $hotplug_error->($opt);
1013 $conf->{$opt} = $value;
1014 } elsif ($opt eq 'nameserver') {
1015 next if $hotplug_error->($opt);
1016 my $list = PVE::LXC::verify_nameserver_list($value);
1017 $conf->{$opt} = $list;
1018 } elsif ($opt eq 'searchdomain') {
1019 next if $hotplug_error->($opt);
1020 my $list = PVE::LXC::verify_searchdomain_list($value);
1021 $conf->{$opt} = $list;
1022 } elsif ($opt eq 'cores') {
1023 $conf->{$opt} = $value;# rest is handled by pvestatd
1024 } elsif ($opt eq 'cpulimit') {
1025 if ($value == 0) {
1026 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1027 } else {
1028 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1029 }
1030 $conf->{$opt} = $value;
1031 } elsif ($opt eq 'cpuunits') {
1032 $conf->{$opt} = $value;
1033 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1034 } elsif ($opt eq 'description') {
1035 $conf->{$opt} = $value;
1036 } elsif ($opt =~ m/^net(\d+)$/) {
1037 my $netid = $1;
1038 my $net = PVE::LXC::Config->parse_lxc_network($value);
1039 if (!$running) {
1040 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
1041 } else {
1042 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1043 }
1044 } elsif ($opt eq 'protection') {
1045 $conf->{$opt} = $value ? 1 : 0;
1046 } elsif ($opt =~ m/^mp(\d+)$/) {
1047 next if $hotplug_error->($opt);
1048 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1049 my $old = $conf->{$opt};
1050 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
1051 if ($mp->{type} eq 'volume') {
1052 &$check_content_type($mp);
1053 $used_volids->{$mp->{volume}} = 1;
1054 &$rescan_volume($mp);
1055 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
1056 } else {
1057 $conf->{$opt} = $value;
1058 }
1059 if (defined($old)) {
1060 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
1061 if ($mp->{type} eq 'volume') {
1062 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1063 }
1064 }
1065 $new_disks = 1;
1066 } elsif ($opt eq 'rootfs') {
1067 next if $hotplug_error->($opt);
1068 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1069 my $old = $conf->{$opt};
1070 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
1071 if ($mp->{type} eq 'volume') {
1072 &$check_content_type($mp);
1073 $used_volids->{$mp->{volume}} = 1;
1074 &$rescan_volume($mp);
1075 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
1076 } else {
1077 $conf->{$opt} = $value;
1078 }
1079 if (defined($old)) {
1080 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
1081 if ($mp->{type} eq 'volume') {
1082 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1083 }
1084 }
1085 $new_disks = 1;
1086 } elsif ($opt eq 'unprivileged') {
1087 die "unable to modify read-only option: '$opt'\n";
1088 } elsif ($opt eq 'ostype') {
1089 next if $hotplug_error->($opt);
1090 $conf->{$opt} = $value;
1091 } elsif ($opt eq 'features') {
1092 next if $hotplug_error->($opt);
1093 $conf->{$opt} = $value;
1094 } else {
1095 die "implement me: $opt";
1096 }
1097
1098 PVE::LXC::Config->write_config($vmid, $conf) if $running;
1099 }
1100
1101 # Apply deletions and creations of new volumes
1102 if (@deleted_volumes) {
1103 my $storage_cfg = PVE::Storage::config();
1104 foreach my $volume (@deleted_volumes) {
1105 next if $used_volids->{$volume}; # could have been re-added, too
1106 # also check for references in snapshots
1107 next if $class->is_volume_in_use($conf, $volume, 1);
1108 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1109 }
1110 }
1111
1112 if ($new_disks) {
1113 my $storage_cfg = PVE::Storage::config();
1114 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
1115 }
1116
1117 # This should be the last thing we do here
1118 if ($running && scalar(@nohotplug)) {
1119 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1120 }
1121 }
1122
1123 sub check_type {
1124 my ($class, $key, $value) = @_;
1125
1126 die "unknown setting '$key'\n" if !$confdesc->{$key};
1127
1128 my $type = $confdesc->{$key}->{type};
1129
1130 if (!defined($value)) {
1131 die "got undefined value\n";
1132 }
1133
1134 if ($value =~ m/[\n\r]/) {
1135 die "property contains a line feed\n";
1136 }
1137
1138 if ($type eq 'boolean') {
1139 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1140 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1141 die "type check ('boolean') failed - got '$value'\n";
1142 } elsif ($type eq 'integer') {
1143 return int($1) if $value =~ m/^(\d+)$/;
1144 die "type check ('integer') failed - got '$value'\n";
1145 } elsif ($type eq 'number') {
1146 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1147 die "type check ('number') failed - got '$value'\n";
1148 } elsif ($type eq 'string') {
1149 if (my $fmt = $confdesc->{$key}->{format}) {
1150 PVE::JSONSchema::check_format($fmt, $value);
1151 return $value;
1152 }
1153 return $value;
1154 } else {
1155 die "internal error"
1156 }
1157 }
1158
1159
1160 # add JSON properties for create and set function
1161 sub json_config_properties {
1162 my ($class, $prop) = @_;
1163
1164 foreach my $opt (keys %$confdesc) {
1165 next if $opt eq 'parent' || $opt eq 'snaptime';
1166 next if $prop->{$opt};
1167 $prop->{$opt} = $confdesc->{$opt};
1168 }
1169
1170 return $prop;
1171 }
1172
1173 sub __parse_ct_mountpoint_full {
1174 my ($class, $desc, $data, $noerr) = @_;
1175
1176 $data //= '';
1177
1178 my $res;
1179 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1180 if ($@) {
1181 return undef if $noerr;
1182 die $@;
1183 }
1184
1185 if (defined(my $size = $res->{size})) {
1186 $size = PVE::JSONSchema::parse_size($size);
1187 if (!defined($size)) {
1188 return undef if $noerr;
1189 die "invalid size: $size\n";
1190 }
1191 $res->{size} = $size;
1192 }
1193
1194 $res->{type} = $class->classify_mountpoint($res->{volume});
1195
1196 return $res;
1197 };
1198
1199 sub parse_ct_rootfs {
1200 my ($class, $data, $noerr) = @_;
1201
1202 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1203
1204 $res->{mp} = '/' if defined($res);
1205
1206 return $res;
1207 }
1208
1209 sub parse_ct_mountpoint {
1210 my ($class, $data, $noerr) = @_;
1211
1212 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1213 }
1214
1215 sub print_ct_mountpoint {
1216 my ($class, $info, $nomp) = @_;
1217 my $skip = [ 'type' ];
1218 push @$skip, 'mp' if $nomp;
1219 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1220 }
1221
1222 sub print_lxc_network {
1223 my ($class, $net) = @_;
1224 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1225 }
1226
1227 sub parse_lxc_network {
1228 my ($class, $data) = @_;
1229
1230 my $res = {};
1231
1232 return $res if !$data;
1233
1234 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1235
1236 $res->{type} = 'veth';
1237 if (!$res->{hwaddr}) {
1238 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1239 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1240 }
1241
1242 return $res;
1243 }
1244
1245 sub parse_features {
1246 my ($class, $data) = @_;
1247 return {} if !$data;
1248 return PVE::JSONSchema::parse_property_string($features_desc, $data);
1249 }
1250
1251 sub option_exists {
1252 my ($class, $name) = @_;
1253
1254 return defined($confdesc->{$name});
1255 }
1256 # END JSON config code
1257
1258 sub classify_mountpoint {
1259 my ($class, $vol) = @_;
1260 if ($vol =~ m!^/!) {
1261 return 'device' if $vol =~ m!^/dev/!;
1262 return 'bind';
1263 }
1264 return 'volume';
1265 }
1266
1267 my $is_volume_in_use = sub {
1268 my ($class, $config, $volid) = @_;
1269 my $used = 0;
1270
1271 $class->foreach_mountpoint($config, sub {
1272 my ($ms, $mountpoint) = @_;
1273 return if $used;
1274 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1275 });
1276
1277 return $used;
1278 };
1279
1280 sub is_volume_in_use_by_snapshots {
1281 my ($class, $config, $volid) = @_;
1282
1283 if (my $snapshots = $config->{snapshots}) {
1284 foreach my $snap (keys %$snapshots) {
1285 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1286 }
1287 }
1288
1289 return 0;
1290 };
1291
1292 sub is_volume_in_use {
1293 my ($class, $config, $volid, $include_snapshots) = @_;
1294 return 1 if $is_volume_in_use->($class, $config, $volid);
1295 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1296 return 0;
1297 }
1298
1299 sub has_dev_console {
1300 my ($class, $conf) = @_;
1301
1302 return !(defined($conf->{console}) && !$conf->{console});
1303 }
1304
1305 sub has_lxc_entry {
1306 my ($class, $conf, $keyname) = @_;
1307
1308 if (my $lxcconf = $conf->{lxc}) {
1309 foreach my $entry (@$lxcconf) {
1310 my ($key, undef) = @$entry;
1311 return 1 if $key eq $keyname;
1312 }
1313 }
1314
1315 return 0;
1316 }
1317
1318 sub get_tty_count {
1319 my ($class, $conf) = @_;
1320
1321 return $conf->{tty} // $confdesc->{tty}->{default};
1322 }
1323
1324 sub get_cmode {
1325 my ($class, $conf) = @_;
1326
1327 return $conf->{cmode} // $confdesc->{cmode}->{default};
1328 }
1329
1330 sub mountpoint_names {
1331 my ($class, $reverse) = @_;
1332
1333 my @names = ('rootfs');
1334
1335 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1336 push @names, "mp$i";
1337 }
1338
1339 return $reverse ? reverse @names : @names;
1340 }
1341
1342 sub foreach_mountpoint_full {
1343 my ($class, $conf, $reverse, $func, @param) = @_;
1344
1345 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1346 foreach my $key (@$mps) {
1347 my $value = $conf->{$key};
1348 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1349 next if !defined($mountpoint);
1350
1351 &$func($key, $mountpoint, @param);
1352 }
1353 }
1354
1355 sub foreach_mountpoint {
1356 my ($class, $conf, $func, @param) = @_;
1357
1358 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1359 }
1360
1361 sub foreach_mountpoint_reverse {
1362 my ($class, $conf, $func, @param) = @_;
1363
1364 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1365 }
1366
1367 sub get_vm_volumes {
1368 my ($class, $conf, $excludes) = @_;
1369
1370 my $vollist = [];
1371
1372 $class->foreach_mountpoint($conf, sub {
1373 my ($ms, $mountpoint) = @_;
1374
1375 return if $excludes && $ms eq $excludes;
1376
1377 my $volid = $mountpoint->{volume};
1378 return if !$volid || $mountpoint->{type} ne 'volume';
1379
1380 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1381 return if !$sid;
1382
1383 push @$vollist, $volid;
1384 });
1385
1386 return $vollist;
1387 }
1388
1389 sub get_replicatable_volumes {
1390 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1391
1392 my $volhash = {};
1393
1394 my $test_volid = sub {
1395 my ($volid, $mountpoint) = @_;
1396
1397 return if !$volid;
1398
1399 my $mptype = $mountpoint->{type};
1400 my $replicate = $mountpoint->{replicate} // 1;
1401
1402 if ($mptype ne 'volume') {
1403 # skip bindmounts if replicate = 0 even for cleanup,
1404 # since bind mounts could not have been replicated ever
1405 return if !$replicate;
1406 die "unable to replicate mountpoint type '$mptype'\n";
1407 }
1408
1409 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1410 return if !$storeid;
1411
1412 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
1413 return if $scfg->{shared};
1414
1415 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1416 return if !$owner || ($owner != $vmid);
1417
1418 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1419
1420 return if !$cleanup && !$replicate;
1421
1422 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
1423 return if $cleanup || $noerr;
1424 die "missing replicate feature on volume '$volid'\n";
1425 }
1426
1427 $volhash->{$volid} = 1;
1428 };
1429
1430 $class->foreach_mountpoint($conf, sub {
1431 my ($ms, $mountpoint) = @_;
1432 $test_volid->($mountpoint->{volume}, $mountpoint);
1433 });
1434
1435 foreach my $snapname (keys %{$conf->{snapshots}}) {
1436 my $snap = $conf->{snapshots}->{$snapname};
1437 $class->foreach_mountpoint($snap, sub {
1438 my ($ms, $mountpoint) = @_;
1439 $test_volid->($mountpoint->{volume}, $mountpoint);
1440 });
1441 }
1442
1443 # add 'unusedX' volumes to volhash
1444 foreach my $key (keys %$conf) {
1445 if ($key =~ m/^unused/) {
1446 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1447 }
1448 }
1449
1450 return $volhash;
1451 }
1452
1453 1;