]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
depreacate pve-lxc-snapshot-name in favor of identical pve-snapshot-name
[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::GuestHelpers;
9 use PVE::INotify;
10 use PVE::JSONSchema qw(get_standard_option);
11 use PVE::Tools;
12
13 use base qw(PVE::AbstractConfig);
14
15 my $nodename = PVE::INotify::nodename();
16 my $lock_handles = {};
17 my $lockdir = "/run/lock/lxc";
18 mkdir $lockdir;
19 mkdir "/etc/pve/nodes/$nodename/lxc";
20 my $MAX_MOUNT_POINTS = 256;
21 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
22
23 # BEGIN implemented abstract methods from PVE::AbstractConfig
24
25 sub guest_type {
26 return "CT";
27 }
28
29 sub __config_max_unused_disks {
30 my ($class) = @_;
31
32 return $MAX_UNUSED_DISKS;
33 }
34
35 sub config_file_lock {
36 my ($class, $vmid) = @_;
37
38 return "$lockdir/pve-config-${vmid}.lock";
39 }
40
41 sub cfs_config_path {
42 my ($class, $vmid, $node) = @_;
43
44 $node = $nodename if !$node;
45 return "nodes/$node/lxc/$vmid.conf";
46 }
47
48 sub mountpoint_backup_enabled {
49 my ($class, $mp_key, $mountpoint) = @_;
50
51 return 1 if $mp_key eq 'rootfs';
52
53 return 0 if $mountpoint->{type} ne 'volume';
54
55 return 1 if $mountpoint->{backup};
56
57 return 0;
58 }
59
60 sub has_feature {
61 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
62 my $err;
63
64 $class->foreach_mountpoint($conf, sub {
65 my ($ms, $mountpoint) = @_;
66
67 return if $err; # skip further test
68 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
69
70 $err = 1
71 if !PVE::Storage::volume_has_feature($storecfg, $feature,
72 $mountpoint->{volume},
73 $snapname, $running);
74 });
75
76 return $err ? 0 : 1;
77 }
78
79 sub __snapshot_save_vmstate {
80 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
81 die "implement me - snapshot_save_vmstate\n";
82 }
83
84 sub __snapshot_check_running {
85 my ($class, $vmid) = @_;
86 return PVE::LXC::check_running($vmid);
87 }
88
89 sub __snapshot_check_freeze_needed {
90 my ($class, $vmid, $config, $save_vmstate) = @_;
91
92 my $ret = $class->__snapshot_check_running($vmid);
93 return ($ret, $ret);
94 }
95
96 sub __snapshot_freeze {
97 my ($class, $vmid, $unfreeze) = @_;
98
99 if ($unfreeze) {
100 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
101 warn $@ if $@;
102 } else {
103 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
104 PVE::LXC::sync_container_namespace($vmid);
105 }
106 }
107
108 sub __snapshot_create_vol_snapshot {
109 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
110
111 my $storecfg = PVE::Storage::config();
112
113 return if $snapname eq 'vzdump' &&
114 !$class->mountpoint_backup_enabled($ms, $mountpoint);
115
116 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
117 }
118
119 sub __snapshot_delete_remove_drive {
120 my ($class, $snap, $remove_drive) = @_;
121
122 if ($remove_drive eq 'vmstate') {
123 die "implement me - saving vmstate\n";
124 } else {
125 my $value = $snap->{$remove_drive};
126 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
127 delete $snap->{$remove_drive};
128
129 $class->add_unused_volume($snap, $mountpoint->{volume})
130 if ($mountpoint->{type} eq 'volume');
131 }
132 }
133
134 sub __snapshot_delete_vmstate_file {
135 my ($class, $snap, $force) = @_;
136
137 die "implement me - saving vmstate\n";
138 }
139
140 sub __snapshot_delete_vol_snapshot {
141 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
142
143 return if $snapname eq 'vzdump' &&
144 !$class->mountpoint_backup_enabled($ms, $mountpoint);
145
146 my $storecfg = PVE::Storage::config();
147 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
148 push @$unused, $mountpoint->{volume};
149 }
150
151 sub __snapshot_rollback_vol_possible {
152 my ($class, $mountpoint, $snapname) = @_;
153
154 my $storecfg = PVE::Storage::config();
155 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
156 }
157
158 sub __snapshot_rollback_vol_rollback {
159 my ($class, $mountpoint, $snapname) = @_;
160
161 my $storecfg = PVE::Storage::config();
162 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
163 }
164
165 sub __snapshot_rollback_vm_stop {
166 my ($class, $vmid) = @_;
167
168 PVE::LXC::vm_stop($vmid, 1)
169 if $class->__snapshot_check_running($vmid);
170 }
171
172 sub __snapshot_rollback_vm_start {
173 my ($class, $vmid, $vmstate, $data);
174
175 die "implement me - save vmstate\n";
176 }
177
178 sub __snapshot_rollback_get_unused {
179 my ($class, $conf, $snap) = @_;
180
181 my $unused = [];
182
183 $class->__snapshot_foreach_volume($conf, sub {
184 my ($vs, $volume) = @_;
185
186 return if $volume->{type} ne 'volume';
187
188 my $found = 0;
189 my $volid = $volume->{volume};
190
191 $class->__snapshot_foreach_volume($snap, sub {
192 my ($ms, $mountpoint) = @_;
193
194 return if $found;
195 return if ($mountpoint->{type} ne 'volume');
196
197 $found = 1
198 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
199 });
200
201 push @$unused, $volid if !$found;
202 });
203
204 return $unused;
205 }
206
207 sub __snapshot_foreach_volume {
208 my ($class, $conf, $func) = @_;
209
210 $class->foreach_mountpoint($conf, $func);
211 }
212
213 # END implemented abstract methods from PVE::AbstractConfig
214
215 # BEGIN JSON config code
216
217 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
218
219 my $rootfs_desc = {
220 volume => {
221 type => 'string',
222 default_key => 1,
223 format => 'pve-lxc-mp-string',
224 format_description => 'volume',
225 description => 'Volume, device or directory to mount into the container.',
226 },
227 size => {
228 type => 'string',
229 format => 'disk-size',
230 format_description => 'DiskSize',
231 description => 'Volume size (read only value).',
232 optional => 1,
233 },
234 acl => {
235 type => 'boolean',
236 description => 'Explicitly enable or disable ACL support.',
237 optional => 1,
238 },
239 ro => {
240 type => 'boolean',
241 description => 'Read-only mount point',
242 optional => 1,
243 },
244 quota => {
245 type => 'boolean',
246 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
247 optional => 1,
248 },
249 replicate => {
250 type => 'boolean',
251 description => 'Will include this volume to a storage replica job.',
252 optional => 1,
253 default => 1,
254 },
255 shared => {
256 type => 'boolean',
257 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
258 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!",
259 optional => 1,
260 default => 0,
261 },
262 };
263
264 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
265 type => 'string', format => $rootfs_desc,
266 description => "Use volume as container root.",
267 optional => 1,
268 });
269
270 my $features_desc = {
271 mount => {
272 optional => 1,
273 type => 'string',
274 description => "Allow mounting file systems of specific types."
275 ." This should be a list of file system types as used with the mount command."
276 ." Note that this can have negative effects on the container's security."
277 ." With access to a loop device, mounting a file can circumvent the mknod"
278 ." permission of the devices cgroup, mounting an NFS file system can"
279 ." block the host's I/O completely and prevent it from rebooting, etc.",
280 format_description => 'fstype;fstype;...',
281 pattern => qr/[a-zA-Z0-9_; ]+/,
282 },
283 nesting => {
284 optional => 1,
285 type => 'boolean',
286 default => 0,
287 description => "Allow nesting."
288 ." Best used with unprivileged containers with additional id mapping."
289 ." Note that this will expose procfs and sysfs contents of the host"
290 ." to the guest.",
291 },
292 keyctl => {
293 optional => 1,
294 type => 'boolean',
295 default => 0,
296 description => "For unprivileged containers only: Allow the use of the keyctl() system call."
297 ." This is required to use docker inside a container."
298 ." By default unprivileged containers will see this system call as non-existent."
299 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
300 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
301 ." Essentially, you can choose between running systemd-networkd or docker.",
302 },
303 fuse => {
304 optional => 1,
305 type => 'boolean',
306 default => 0,
307 description => "Allow using 'fuse' file systems in a container."
308 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
309 },
310 };
311
312 my $confdesc = {
313 lock => {
314 optional => 1,
315 type => 'string',
316 description => "Lock/unlock the VM.",
317 enum => [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
318 },
319 onboot => {
320 optional => 1,
321 type => 'boolean',
322 description => "Specifies whether a VM will be started during system bootup.",
323 default => 0,
324 },
325 startup => get_standard_option('pve-startup-order'),
326 template => {
327 optional => 1,
328 type => 'boolean',
329 description => "Enable/disable Template.",
330 default => 0,
331 },
332 arch => {
333 optional => 1,
334 type => 'string',
335 enum => ['amd64', 'i386', 'arm64', 'armhf'],
336 description => "OS architecture type.",
337 default => 'amd64',
338 },
339 ostype => {
340 optional => 1,
341 type => 'string',
342 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
343 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.",
344 },
345 console => {
346 optional => 1,
347 type => 'boolean',
348 description => "Attach a console device (/dev/console) to the container.",
349 default => 1,
350 },
351 tty => {
352 optional => 1,
353 type => 'integer',
354 description => "Specify the number of tty available to the container",
355 minimum => 0,
356 maximum => 6,
357 default => 2,
358 },
359 cores => {
360 optional => 1,
361 type => 'integer',
362 description => "The number of cores assigned to the container. A container can use all available cores by default.",
363 minimum => 1,
364 maximum => 128,
365 },
366 cpulimit => {
367 optional => 1,
368 type => 'number',
369 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.",
370 minimum => 0,
371 maximum => 128,
372 default => 0,
373 },
374 cpuunits => {
375 optional => 1,
376 type => 'integer',
377 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.",
378 minimum => 0,
379 maximum => 500000,
380 default => 1024,
381 },
382 memory => {
383 optional => 1,
384 type => 'integer',
385 description => "Amount of RAM for the VM in MB.",
386 minimum => 16,
387 default => 512,
388 },
389 swap => {
390 optional => 1,
391 type => 'integer',
392 description => "Amount of SWAP for the VM in MB.",
393 minimum => 0,
394 default => 512,
395 },
396 hostname => {
397 optional => 1,
398 description => "Set a host name for the container.",
399 type => 'string', format => 'dns-name',
400 maxLength => 255,
401 },
402 description => {
403 optional => 1,
404 type => 'string',
405 description => "Container description. Only used on the configuration web interface.",
406 },
407 searchdomain => {
408 optional => 1,
409 type => 'string', format => 'dns-name-list',
410 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
411 },
412 nameserver => {
413 optional => 1,
414 type => 'string', format => 'address-list',
415 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.",
416 },
417 rootfs => get_standard_option('pve-ct-rootfs'),
418 parent => {
419 optional => 1,
420 type => 'string', format => 'pve-configid',
421 maxLength => 40,
422 description => "Parent snapshot name. This is used internally, and should not be modified.",
423 },
424 snaptime => {
425 optional => 1,
426 description => "Timestamp for snapshots.",
427 type => 'integer',
428 minimum => 0,
429 },
430 cmode => {
431 optional => 1,
432 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).",
433 type => 'string',
434 enum => ['shell', 'console', 'tty'],
435 default => 'tty',
436 },
437 protection => {
438 optional => 1,
439 type => 'boolean',
440 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
441 default => 0,
442 },
443 unprivileged => {
444 optional => 1,
445 type => 'boolean',
446 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
447 default => 0,
448 },
449 features => {
450 optional => 1,
451 type => 'string',
452 format => $features_desc,
453 description => "Allow containers access to advanced features.",
454 },
455 hookscript => {
456 optional => 1,
457 type => 'string',
458 format => 'pve-volume-id',
459 description => 'Script that will be exectued during various steps in the containers lifetime.',
460 },
461 };
462
463 my $valid_lxc_conf_keys = {
464 'lxc.apparmor.profile' => 1,
465 'lxc.apparmor.allow_incomplete' => 1,
466 'lxc.apparmor.allow_nesting' => 1,
467 'lxc.apparmor.raw' => 1,
468 'lxc.selinux.context' => 1,
469 'lxc.include' => 1,
470 'lxc.arch' => 1,
471 'lxc.uts.name' => 1,
472 'lxc.signal.halt' => 1,
473 'lxc.signal.reboot' => 1,
474 'lxc.signal.stop' => 1,
475 'lxc.init.cmd' => 1,
476 'lxc.pty.max' => 1,
477 'lxc.console.logfile' => 1,
478 'lxc.console.path' => 1,
479 'lxc.tty.max' => 1,
480 'lxc.devtty.dir' => 1,
481 'lxc.hook.autodev' => 1,
482 'lxc.autodev' => 1,
483 'lxc.kmsg' => 1,
484 'lxc.mount.fstab' => 1,
485 'lxc.mount.entry' => 1,
486 'lxc.mount.auto' => 1,
487 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
488 'lxc.rootfs.mount' => 1,
489 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
490 ', please use mount point options in the "rootfs" key',
491 # lxc.cgroup.*
492 # lxc.prlimit.*
493 # lxc.net.*
494 'lxc.cap.drop' => 1,
495 'lxc.cap.keep' => 1,
496 'lxc.seccomp.profile' => 1,
497 'lxc.idmap' => 1,
498 'lxc.hook.pre-start' => 1,
499 'lxc.hook.pre-mount' => 1,
500 'lxc.hook.mount' => 1,
501 'lxc.hook.start' => 1,
502 'lxc.hook.stop' => 1,
503 'lxc.hook.post-stop' => 1,
504 'lxc.hook.clone' => 1,
505 'lxc.hook.destroy' => 1,
506 'lxc.log.level' => 1,
507 'lxc.log.file' => 1,
508 'lxc.start.auto' => 1,
509 'lxc.start.delay' => 1,
510 'lxc.start.order' => 1,
511 'lxc.group' => 1,
512 'lxc.environment' => 1,
513
514 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
515 'lxc.sysctl.fs.mqueue' => 1,
516 'lxc.sysctl.kernel.msgmax' => 1,
517 'lxc.sysctl.kernel.msgmnb' => 1,
518 'lxc.sysctl.kernel.msgmni' => 1,
519 'lxc.sysctl.kernel.sem' => 1,
520 'lxc.sysctl.kernel.shmall' => 1,
521 'lxc.sysctl.kernel.shmmax' => 1,
522 'lxc.sysctl.kernel.shmmni' => 1,
523 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
524 };
525
526 my $deprecated_lxc_conf_keys = {
527 # Deprecated (removed with lxc 3.0):
528 'lxc.aa_profile' => 'lxc.apparmor.profile',
529 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
530 'lxc.console' => 'lxc.console.path',
531 'lxc.devttydir' => 'lxc.tty.dir',
532 'lxc.haltsignal' => 'lxc.signal.halt',
533 'lxc.rebootsignal' => 'lxc.signal.reboot',
534 'lxc.stopsignal' => 'lxc.signal.stop',
535 'lxc.id_map' => 'lxc.idmap',
536 'lxc.init_cmd' => 'lxc.init.cmd',
537 'lxc.loglevel' => 'lxc.log.level',
538 'lxc.logfile' => 'lxc.log.file',
539 'lxc.mount' => 'lxc.mount.fstab',
540 'lxc.network.type' => 'lxc.net.INDEX.type',
541 'lxc.network.flags' => 'lxc.net.INDEX.flags',
542 'lxc.network.link' => 'lxc.net.INDEX.link',
543 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
544 'lxc.network.name' => 'lxc.net.INDEX.name',
545 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
546 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
547 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
548 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
549 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
550 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
551 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
552 'lxc.pts' => 'lxc.pty.max',
553 'lxc.se_context' => 'lxc.selinux.context',
554 'lxc.seccomp' => 'lxc.seccomp.profile',
555 'lxc.tty' => 'lxc.tty.max',
556 'lxc.utsname' => 'lxc.uts.name',
557 };
558
559 sub is_valid_lxc_conf_key {
560 my ($vmid, $key) = @_;
561 if ($key =~ /^lxc\.limit\./) {
562 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
563 return 1;
564 }
565 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
566 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
567 return 1;
568 }
569 my $validity = $valid_lxc_conf_keys->{$key};
570 return $validity if defined($validity);
571 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
572 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
573 || $key =~ /^lxc\.net\./; # allow custom network definitions
574 return 0;
575 }
576
577 our $netconf_desc = {
578 type => {
579 type => 'string',
580 optional => 1,
581 description => "Network interface type.",
582 enum => [qw(veth)],
583 },
584 name => {
585 type => 'string',
586 format_description => 'string',
587 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
588 pattern => '[-_.\w\d]+',
589 },
590 bridge => {
591 type => 'string',
592 format_description => 'bridge',
593 description => 'Bridge to attach the network device to.',
594 pattern => '[-_.\w\d]+',
595 optional => 1,
596 },
597 hwaddr => get_standard_option('mac-addr', {
598 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)',
599 }),
600 mtu => {
601 type => 'integer',
602 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
603 minimum => 64, # minimum ethernet frame is 64 bytes
604 optional => 1,
605 },
606 ip => {
607 type => 'string',
608 format => 'pve-ipv4-config',
609 format_description => '(IPv4/CIDR|dhcp|manual)',
610 description => 'IPv4 address in CIDR format.',
611 optional => 1,
612 },
613 gw => {
614 type => 'string',
615 format => 'ipv4',
616 format_description => 'GatewayIPv4',
617 description => 'Default gateway for IPv4 traffic.',
618 optional => 1,
619 },
620 ip6 => {
621 type => 'string',
622 format => 'pve-ipv6-config',
623 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
624 description => 'IPv6 address in CIDR format.',
625 optional => 1,
626 },
627 gw6 => {
628 type => 'string',
629 format => 'ipv6',
630 format_description => 'GatewayIPv6',
631 description => 'Default gateway for IPv6 traffic.',
632 optional => 1,
633 },
634 firewall => {
635 type => 'boolean',
636 description => "Controls whether this interface's firewall rules should be used.",
637 optional => 1,
638 },
639 tag => {
640 type => 'integer',
641 minimum => 1,
642 maximum => 4094,
643 description => "VLAN tag for this interface.",
644 optional => 1,
645 },
646 trunks => {
647 type => 'string',
648 pattern => qr/\d+(?:;\d+)*/,
649 format_description => 'vlanid[;vlanid...]',
650 description => "VLAN ids to pass through the interface",
651 optional => 1,
652 },
653 rate => {
654 type => 'number',
655 format_description => 'mbps',
656 description => "Apply rate limiting to the interface",
657 optional => 1,
658 },
659 };
660 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
661
662 my $MAX_LXC_NETWORKS = 10;
663 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
664 $confdesc->{"net$i"} = {
665 optional => 1,
666 type => 'string', format => $netconf_desc,
667 description => "Specifies network interfaces for the container.",
668 };
669 }
670
671 PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
672 sub verify_lxc_mp_string {
673 my ($mp, $noerr) = @_;
674
675 # do not allow:
676 # /./ or /../
677 # /. or /.. at the end
678 # ../ at the beginning
679
680 if($mp =~ m@/\.\.?/@ ||
681 $mp =~ m@/\.\.?$@ ||
682 $mp =~ m@^\.\./@) {
683 return undef if $noerr;
684 die "$mp contains illegal character sequences\n";
685 }
686 return $mp;
687 }
688
689 my $mp_desc = {
690 %$rootfs_desc,
691 backup => {
692 type => 'boolean',
693 description => 'Whether to include the mount point in backups.',
694 verbose_description => 'Whether to include the mount point in backups '.
695 '(only used for volume mount points).',
696 optional => 1,
697 },
698 mp => {
699 type => 'string',
700 format => 'pve-lxc-mp-string',
701 format_description => 'Path',
702 description => 'Path to the mount point as seen from inside the container '.
703 '(must not contain symlinks).',
704 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
705 "NOTE: Must not contain any symlinks for security reasons."
706 },
707 };
708 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
709
710 my $unuseddesc = {
711 optional => 1,
712 type => 'string', format => 'pve-volume-id',
713 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
714 };
715
716 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
717 $confdesc->{"mp$i"} = {
718 optional => 1,
719 type => 'string', format => $mp_desc,
720 description => "Use volume as container mount point.",
721 optional => 1,
722 };
723 }
724
725 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
726 $confdesc->{"unused$i"} = $unuseddesc;
727 }
728
729 sub parse_pct_config {
730 my ($filename, $raw) = @_;
731
732 return undef if !defined($raw);
733
734 my $res = {
735 digest => Digest::SHA::sha1_hex($raw),
736 snapshots => {},
737 };
738
739 $filename =~ m|/lxc/(\d+).conf$|
740 || die "got strange filename '$filename'";
741
742 my $vmid = $1;
743
744 my $conf = $res;
745 my $descr = '';
746 my $section = '';
747
748 my @lines = split(/\n/, $raw);
749 foreach my $line (@lines) {
750 next if $line =~ m/^\s*$/;
751
752 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
753 $section = $1;
754 $conf->{description} = $descr if $descr;
755 $descr = '';
756 $conf = $res->{snapshots}->{$section} = {};
757 next;
758 }
759
760 if ($line =~ m/^\#(.*)\s*$/) {
761 $descr .= PVE::Tools::decode_text($1) . "\n";
762 next;
763 }
764
765 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
766 my $key = $1;
767 my $value = $3;
768 my $validity = is_valid_lxc_conf_key($vmid, $key);
769 if ($validity eq 1) {
770 push @{$conf->{lxc}}, [$key, $value];
771 } elsif (my $errmsg = $validity) {
772 warn "vm $vmid - $key: $errmsg\n";
773 } else {
774 warn "vm $vmid - unable to parse config: $line\n";
775 }
776 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
777 $descr .= PVE::Tools::decode_text($2);
778 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
779 $conf->{snapstate} = $1;
780 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
781 my $key = $1;
782 my $value = $2;
783 eval { $value = PVE::LXC::Config->check_type($key, $value); };
784 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
785 $conf->{$key} = $value;
786 } else {
787 warn "vm $vmid - unable to parse config: $line\n";
788 }
789 }
790
791 $conf->{description} = $descr if $descr;
792
793 delete $res->{snapstate}; # just to be sure
794
795 return $res;
796 }
797
798 sub write_pct_config {
799 my ($filename, $conf) = @_;
800
801 delete $conf->{snapstate}; # just to be sure
802
803 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
804 my $used_volids = {};
805 foreach my $vid (@$volidlist) {
806 $used_volids->{$vid} = 1;
807 }
808
809 # remove 'unusedX' settings if the volume is still used
810 foreach my $key (keys %$conf) {
811 my $value = $conf->{$key};
812 if ($key =~ m/^unused/ && $used_volids->{$value}) {
813 delete $conf->{$key};
814 }
815 }
816
817 my $generate_raw_config = sub {
818 my ($conf) = @_;
819
820 my $raw = '';
821
822 # add description as comment to top of file
823 my $descr = $conf->{description} || '';
824 foreach my $cl (split(/\n/, $descr)) {
825 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
826 }
827
828 foreach my $key (sort keys %$conf) {
829 next if $key eq 'digest' || $key eq 'description' ||
830 $key eq 'pending' || $key eq 'snapshots' ||
831 $key eq 'snapname' || $key eq 'lxc';
832 my $value = $conf->{$key};
833 die "detected invalid newline inside property '$key'\n"
834 if $value =~ m/\n/;
835 $raw .= "$key: $value\n";
836 }
837
838 if (my $lxcconf = $conf->{lxc}) {
839 foreach my $entry (@$lxcconf) {
840 my ($k, $v) = @$entry;
841 $raw .= "$k: $v\n";
842 }
843 }
844
845 return $raw;
846 };
847
848 my $raw = &$generate_raw_config($conf);
849
850 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
851 $raw .= "\n[$snapname]\n";
852 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
853 }
854
855 return $raw;
856 }
857
858 sub update_pct_config {
859 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
860
861 my @nohotplug;
862
863 my $new_disks = 0;
864 my @deleted_volumes;
865
866 my $rootdir;
867 if ($running) {
868 my $pid = PVE::LXC::find_lxc_pid($vmid);
869 $rootdir = "/proc/$pid/root";
870 }
871
872 my $hotplug_error = sub {
873 if ($running) {
874 push @nohotplug, @_;
875 return 1;
876 } else {
877 return 0;
878 }
879 };
880
881 if (defined($delete)) {
882 foreach my $opt (@$delete) {
883 if (!exists($conf->{$opt})) {
884 # silently ignore
885 next;
886 }
887
888 if ($opt eq 'memory' || $opt eq 'rootfs') {
889 die "unable to delete required option '$opt'\n";
890 } elsif ($opt eq 'hostname') {
891 delete $conf->{$opt};
892 } elsif ($opt eq 'swap') {
893 delete $conf->{$opt};
894 PVE::LXC::write_cgroup_value("memory", $vmid,
895 "memory.memsw.limit_in_bytes", -1);
896 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup' || $opt eq 'hookscript') {
897 delete $conf->{$opt};
898 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
899 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
900 next if $hotplug_error->($opt);
901 delete $conf->{$opt};
902 } elsif ($opt eq 'cores') {
903 delete $conf->{$opt}; # rest is handled by pvestatd
904 } elsif ($opt eq 'cpulimit') {
905 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
906 delete $conf->{$opt};
907 } elsif ($opt eq 'cpuunits') {
908 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
909 delete $conf->{$opt};
910 } elsif ($opt =~ m/^net(\d)$/) {
911 delete $conf->{$opt};
912 next if !$running;
913 my $netid = $1;
914 PVE::Network::veth_delete("veth${vmid}i$netid");
915 } elsif ($opt eq 'protection') {
916 delete $conf->{$opt};
917 } elsif ($opt =~ m/^unused(\d+)$/) {
918 next if $hotplug_error->($opt);
919 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
920 push @deleted_volumes, $conf->{$opt};
921 delete $conf->{$opt};
922 } elsif ($opt =~ m/^mp(\d+)$/) {
923 next if $hotplug_error->($opt);
924 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
925 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
926 delete $conf->{$opt};
927 if ($mp->{type} eq 'volume') {
928 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
929 }
930 } elsif ($opt eq 'unprivileged') {
931 die "unable to delete read-only option: '$opt'\n";
932 } elsif ($opt eq 'features') {
933 next if $hotplug_error->($opt);
934 delete $conf->{$opt};
935 } else {
936 die "implement me (delete: $opt)"
937 }
938 PVE::LXC::Config->write_config($vmid, $conf) if $running;
939 }
940 }
941
942 # There's no separate swap size to configure, there's memory and "total"
943 # memory (iow. memory+swap). This means we have to change them together.
944 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
945 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
946 if (defined($wanted_memory) || defined($wanted_swap)) {
947
948 my $old_memory = ($conf->{memory} || 512);
949 my $old_swap = ($conf->{swap} || 0);
950
951 $wanted_memory //= $old_memory;
952 $wanted_swap //= $old_swap;
953
954 my $total = $wanted_memory + $wanted_swap;
955 if ($running) {
956 my $old_total = $old_memory + $old_swap;
957 if ($total > $old_total) {
958 PVE::LXC::write_cgroup_value("memory", $vmid,
959 "memory.memsw.limit_in_bytes",
960 int($total*1024*1024));
961 PVE::LXC::write_cgroup_value("memory", $vmid,
962 "memory.limit_in_bytes",
963 int($wanted_memory*1024*1024));
964 } else {
965 PVE::LXC::write_cgroup_value("memory", $vmid,
966 "memory.limit_in_bytes",
967 int($wanted_memory*1024*1024));
968 PVE::LXC::write_cgroup_value("memory", $vmid,
969 "memory.memsw.limit_in_bytes",
970 int($total*1024*1024));
971 }
972 }
973 $conf->{memory} = $wanted_memory;
974 $conf->{swap} = $wanted_swap;
975
976 PVE::LXC::Config->write_config($vmid, $conf) if $running;
977 }
978
979 my $storecfg = PVE::Storage::config();
980
981 my $used_volids = {};
982 my $check_content_type = sub {
983 my ($mp) = @_;
984 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
985 my $storage_config = PVE::Storage::storage_config($storecfg, $sid);
986 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
987 if !$storage_config->{content}->{rootdir};
988 };
989
990 my $rescan_volume = sub {
991 my ($mp) = @_;
992 eval {
993 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
994 if !defined($mp->{size});
995 };
996 warn "Could not rescan volume size - $@\n" if $@;
997 };
998
999 foreach my $opt (keys %$param) {
1000 my $value = $param->{$opt};
1001 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
1002 if ($opt eq 'hostname' || $opt eq 'arch') {
1003 $conf->{$opt} = $value;
1004 } elsif ($opt eq 'onboot') {
1005 $conf->{$opt} = $value ? 1 : 0;
1006 } elsif ($opt eq 'startup') {
1007 $conf->{$opt} = $value;
1008 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1009 next if $hotplug_error->($opt);
1010 $conf->{$opt} = $value;
1011 } elsif ($opt eq 'nameserver') {
1012 next if $hotplug_error->($opt);
1013 my $list = PVE::LXC::verify_nameserver_list($value);
1014 $conf->{$opt} = $list;
1015 } elsif ($opt eq 'searchdomain') {
1016 next if $hotplug_error->($opt);
1017 my $list = PVE::LXC::verify_searchdomain_list($value);
1018 $conf->{$opt} = $list;
1019 } elsif ($opt eq 'cores') {
1020 $conf->{$opt} = $value;# rest is handled by pvestatd
1021 } elsif ($opt eq 'cpulimit') {
1022 if ($value == 0) {
1023 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1024 } else {
1025 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1026 }
1027 $conf->{$opt} = $value;
1028 } elsif ($opt eq 'cpuunits') {
1029 $conf->{$opt} = $value;
1030 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1031 } elsif ($opt eq 'description') {
1032 $conf->{$opt} = $value;
1033 } elsif ($opt =~ m/^net(\d+)$/) {
1034 my $netid = $1;
1035 my $net = PVE::LXC::Config->parse_lxc_network($value);
1036 if (!$running) {
1037 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
1038 } else {
1039 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1040 }
1041 } elsif ($opt eq 'protection') {
1042 $conf->{$opt} = $value ? 1 : 0;
1043 } elsif ($opt =~ m/^mp(\d+)$/) {
1044 next if $hotplug_error->($opt);
1045 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1046 my $old = $conf->{$opt};
1047 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
1048 if ($mp->{type} eq 'volume') {
1049 &$check_content_type($mp);
1050 $used_volids->{$mp->{volume}} = 1;
1051 &$rescan_volume($mp);
1052 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
1053 } else {
1054 $conf->{$opt} = $value;
1055 }
1056 if (defined($old)) {
1057 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
1058 if ($mp->{type} eq 'volume') {
1059 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1060 }
1061 }
1062 $new_disks = 1;
1063 } elsif ($opt eq 'rootfs') {
1064 next if $hotplug_error->($opt);
1065 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1066 my $old = $conf->{$opt};
1067 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
1068 if ($mp->{type} eq 'volume') {
1069 &$check_content_type($mp);
1070 $used_volids->{$mp->{volume}} = 1;
1071 &$rescan_volume($mp);
1072 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
1073 } else {
1074 $conf->{$opt} = $value;
1075 }
1076 if (defined($old)) {
1077 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
1078 if ($mp->{type} eq 'volume') {
1079 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1080 }
1081 }
1082 $new_disks = 1;
1083 } elsif ($opt eq 'unprivileged') {
1084 die "unable to modify read-only option: '$opt'\n";
1085 } elsif ($opt eq 'ostype') {
1086 next if $hotplug_error->($opt);
1087 $conf->{$opt} = $value;
1088 } elsif ($opt eq 'features') {
1089 next if $hotplug_error->($opt);
1090 $conf->{$opt} = $value;
1091 } elsif ($opt eq 'hookscript') {
1092 PVE::GuestHelpers::check_hookscript($value);
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;