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