]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
add informative comment...
[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
514 my $deprecated_lxc_conf_keys = {
515 # Deprecated (removed with lxc 3.0):
516 'lxc.aa_profile' => 'lxc.apparmor.profile',
517 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
518 'lxc.console' => 'lxc.console.path',
519 'lxc.devttydir' => 'lxc.tty.dir',
520 'lxc.haltsignal' => 'lxc.signal.halt',
521 'lxc.rebootsignal' => 'lxc.signal.reboot',
522 'lxc.stopsignal' => 'lxc.signal.stop',
523 'lxc.id_map' => 'lxc.idmap',
524 'lxc.init_cmd' => 'lxc.init.cmd',
525 'lxc.loglevel' => 'lxc.log.level',
526 'lxc.logfile' => 'lxc.log.file',
527 'lxc.mount' => 'lxc.mount.fstab',
528 'lxc.network.type' => 'lxc.net.INDEX.type',
529 'lxc.network.flags' => 'lxc.net.INDEX.flags',
530 'lxc.network.link' => 'lxc.net.INDEX.link',
531 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
532 'lxc.network.name' => 'lxc.net.INDEX.name',
533 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
534 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
535 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
536 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
537 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
538 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
539 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
540 'lxc.pts' => 'lxc.pty.max',
541 'lxc.se_context' => 'lxc.selinux.context',
542 'lxc.seccomp' => 'lxc.seccomp.profile',
543 'lxc.tty' => 'lxc.tty.max',
544 'lxc.utsname' => 'lxc.uts.name',
545 };
546
547 sub is_valid_lxc_conf_key {
548 my ($vmid, $key) = @_;
549 if ($key =~ /^lxc\.limit\./) {
550 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
551 return 1;
552 }
553 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
554 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
555 return 1;
556 }
557 my $validity = $valid_lxc_conf_keys->{$key};
558 return $validity if defined($validity);
559 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
560 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
561 || $key =~ /^lxc\.net\./; # allow custom network definitions
562 return 0;
563 }
564
565 our $netconf_desc = {
566 type => {
567 type => 'string',
568 optional => 1,
569 description => "Network interface type.",
570 enum => [qw(veth)],
571 },
572 name => {
573 type => 'string',
574 format_description => 'string',
575 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
576 pattern => '[-_.\w\d]+',
577 },
578 bridge => {
579 type => 'string',
580 format_description => 'bridge',
581 description => 'Bridge to attach the network device to.',
582 pattern => '[-_.\w\d]+',
583 optional => 1,
584 },
585 hwaddr => {
586 type => 'string',
587 format_description => "XX:XX:XX:XX:XX:XX",
588 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)',
589 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
590 optional => 1,
591 },
592 mtu => {
593 type => 'integer',
594 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
595 minimum => 64, # minimum ethernet frame is 64 bytes
596 optional => 1,
597 },
598 ip => {
599 type => 'string',
600 format => 'pve-ipv4-config',
601 format_description => '(IPv4/CIDR|dhcp|manual)',
602 description => 'IPv4 address in CIDR format.',
603 optional => 1,
604 },
605 gw => {
606 type => 'string',
607 format => 'ipv4',
608 format_description => 'GatewayIPv4',
609 description => 'Default gateway for IPv4 traffic.',
610 optional => 1,
611 },
612 ip6 => {
613 type => 'string',
614 format => 'pve-ipv6-config',
615 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
616 description => 'IPv6 address in CIDR format.',
617 optional => 1,
618 },
619 gw6 => {
620 type => 'string',
621 format => 'ipv6',
622 format_description => 'GatewayIPv6',
623 description => 'Default gateway for IPv6 traffic.',
624 optional => 1,
625 },
626 firewall => {
627 type => 'boolean',
628 description => "Controls whether this interface's firewall rules should be used.",
629 optional => 1,
630 },
631 tag => {
632 type => 'integer',
633 minimum => 1,
634 maximum => 4094,
635 description => "VLAN tag for this interface.",
636 optional => 1,
637 },
638 trunks => {
639 type => 'string',
640 pattern => qr/\d+(?:;\d+)*/,
641 format_description => 'vlanid[;vlanid...]',
642 description => "VLAN ids to pass through the interface",
643 optional => 1,
644 },
645 rate => {
646 type => 'number',
647 format_description => 'mbps',
648 description => "Apply rate limiting to the interface",
649 optional => 1,
650 },
651 };
652 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
653
654 my $MAX_LXC_NETWORKS = 10;
655 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
656 $confdesc->{"net$i"} = {
657 optional => 1,
658 type => 'string', format => $netconf_desc,
659 description => "Specifies network interfaces for the container.",
660 };
661 }
662
663 PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
664 sub verify_lxc_mp_string {
665 my ($mp, $noerr) = @_;
666
667 # do not allow:
668 # /./ or /../
669 # /. or /.. at the end
670 # ../ at the beginning
671
672 if($mp =~ m@/\.\.?/@ ||
673 $mp =~ m@/\.\.?$@ ||
674 $mp =~ m@^\.\./@) {
675 return undef if $noerr;
676 die "$mp contains illegal character sequences\n";
677 }
678 return $mp;
679 }
680
681 my $mp_desc = {
682 %$rootfs_desc,
683 backup => {
684 type => 'boolean',
685 description => 'Whether to include the mount point in backups.',
686 verbose_description => 'Whether to include the mount point in backups '.
687 '(only used for volume mount points).',
688 optional => 1,
689 },
690 mp => {
691 type => 'string',
692 format => 'pve-lxc-mp-string',
693 format_description => 'Path',
694 description => 'Path to the mount point as seen from inside the container '.
695 '(must not contain symlinks).',
696 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
697 "NOTE: Must not contain any symlinks for security reasons."
698 },
699 };
700 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
701
702 my $unuseddesc = {
703 optional => 1,
704 type => 'string', format => 'pve-volume-id',
705 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
706 };
707
708 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
709 $confdesc->{"mp$i"} = {
710 optional => 1,
711 type => 'string', format => $mp_desc,
712 description => "Use volume as container mount point.",
713 optional => 1,
714 };
715 }
716
717 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
718 $confdesc->{"unused$i"} = $unuseddesc;
719 }
720
721 sub parse_pct_config {
722 my ($filename, $raw) = @_;
723
724 return undef if !defined($raw);
725
726 my $res = {
727 digest => Digest::SHA::sha1_hex($raw),
728 snapshots => {},
729 };
730
731 $filename =~ m|/lxc/(\d+).conf$|
732 || die "got strange filename '$filename'";
733
734 my $vmid = $1;
735
736 my $conf = $res;
737 my $descr = '';
738 my $section = '';
739
740 my @lines = split(/\n/, $raw);
741 foreach my $line (@lines) {
742 next if $line =~ m/^\s*$/;
743
744 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
745 $section = $1;
746 $conf->{description} = $descr if $descr;
747 $descr = '';
748 $conf = $res->{snapshots}->{$section} = {};
749 next;
750 }
751
752 if ($line =~ m/^\#(.*)\s*$/) {
753 $descr .= PVE::Tools::decode_text($1) . "\n";
754 next;
755 }
756
757 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
758 my $key = $1;
759 my $value = $3;
760 my $validity = is_valid_lxc_conf_key($vmid, $key);
761 if ($validity eq 1) {
762 push @{$conf->{lxc}}, [$key, $value];
763 } elsif (my $errmsg = $validity) {
764 warn "vm $vmid - $key: $errmsg\n";
765 } else {
766 warn "vm $vmid - unable to parse config: $line\n";
767 }
768 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
769 $descr .= PVE::Tools::decode_text($2);
770 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
771 $conf->{snapstate} = $1;
772 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
773 my $key = $1;
774 my $value = $2;
775 eval { $value = PVE::LXC::Config->check_type($key, $value); };
776 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
777 $conf->{$key} = $value;
778 } else {
779 warn "vm $vmid - unable to parse config: $line\n";
780 }
781 }
782
783 $conf->{description} = $descr if $descr;
784
785 delete $res->{snapstate}; # just to be sure
786
787 return $res;
788 }
789
790 sub write_pct_config {
791 my ($filename, $conf) = @_;
792
793 delete $conf->{snapstate}; # just to be sure
794
795 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
796 my $used_volids = {};
797 foreach my $vid (@$volidlist) {
798 $used_volids->{$vid} = 1;
799 }
800
801 # remove 'unusedX' settings if the volume is still used
802 foreach my $key (keys %$conf) {
803 my $value = $conf->{$key};
804 if ($key =~ m/^unused/ && $used_volids->{$value}) {
805 delete $conf->{$key};
806 }
807 }
808
809 my $generate_raw_config = sub {
810 my ($conf) = @_;
811
812 my $raw = '';
813
814 # add description as comment to top of file
815 my $descr = $conf->{description} || '';
816 foreach my $cl (split(/\n/, $descr)) {
817 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
818 }
819
820 foreach my $key (sort keys %$conf) {
821 next if $key eq 'digest' || $key eq 'description' ||
822 $key eq 'pending' || $key eq 'snapshots' ||
823 $key eq 'snapname' || $key eq 'lxc';
824 my $value = $conf->{$key};
825 die "detected invalid newline inside property '$key'\n"
826 if $value =~ m/\n/;
827 $raw .= "$key: $value\n";
828 }
829
830 if (my $lxcconf = $conf->{lxc}) {
831 foreach my $entry (@$lxcconf) {
832 my ($k, $v) = @$entry;
833 $raw .= "$k: $v\n";
834 }
835 }
836
837 return $raw;
838 };
839
840 my $raw = &$generate_raw_config($conf);
841
842 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
843 $raw .= "\n[$snapname]\n";
844 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
845 }
846
847 return $raw;
848 }
849
850 sub update_pct_config {
851 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
852
853 my @nohotplug;
854
855 my $new_disks = 0;
856 my @deleted_volumes;
857
858 my $rootdir;
859 if ($running) {
860 my $pid = PVE::LXC::find_lxc_pid($vmid);
861 $rootdir = "/proc/$pid/root";
862 }
863
864 my $hotplug_error = sub {
865 if ($running) {
866 push @nohotplug, @_;
867 return 1;
868 } else {
869 return 0;
870 }
871 };
872
873 if (defined($delete)) {
874 foreach my $opt (@$delete) {
875 if (!exists($conf->{$opt})) {
876 # silently ignore
877 next;
878 }
879
880 if ($opt eq 'memory' || $opt eq 'rootfs') {
881 die "unable to delete required option '$opt'\n";
882 } elsif ($opt eq 'hostname') {
883 delete $conf->{$opt};
884 } elsif ($opt eq 'swap') {
885 delete $conf->{$opt};
886 PVE::LXC::write_cgroup_value("memory", $vmid,
887 "memory.memsw.limit_in_bytes", -1);
888 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
889 delete $conf->{$opt};
890 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
891 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
892 next if $hotplug_error->($opt);
893 delete $conf->{$opt};
894 } elsif ($opt eq 'cores') {
895 delete $conf->{$opt}; # rest is handled by pvestatd
896 } elsif ($opt eq 'cpulimit') {
897 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
898 delete $conf->{$opt};
899 } elsif ($opt eq 'cpuunits') {
900 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
901 delete $conf->{$opt};
902 } elsif ($opt =~ m/^net(\d)$/) {
903 delete $conf->{$opt};
904 next if !$running;
905 my $netid = $1;
906 PVE::Network::veth_delete("veth${vmid}i$netid");
907 } elsif ($opt eq 'protection') {
908 delete $conf->{$opt};
909 } elsif ($opt =~ m/^unused(\d+)$/) {
910 next if $hotplug_error->($opt);
911 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
912 push @deleted_volumes, $conf->{$opt};
913 delete $conf->{$opt};
914 } elsif ($opt =~ m/^mp(\d+)$/) {
915 next if $hotplug_error->($opt);
916 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
917 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
918 delete $conf->{$opt};
919 if ($mp->{type} eq 'volume') {
920 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
921 }
922 } elsif ($opt eq 'unprivileged') {
923 die "unable to delete read-only option: '$opt'\n";
924 } elsif ($opt eq 'features') {
925 next if $hotplug_error->($opt);
926 delete $conf->{$opt};
927 } else {
928 die "implement me (delete: $opt)"
929 }
930 PVE::LXC::Config->write_config($vmid, $conf) if $running;
931 }
932 }
933
934 # There's no separate swap size to configure, there's memory and "total"
935 # memory (iow. memory+swap). This means we have to change them together.
936 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
937 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
938 if (defined($wanted_memory) || defined($wanted_swap)) {
939
940 my $old_memory = ($conf->{memory} || 512);
941 my $old_swap = ($conf->{swap} || 0);
942
943 $wanted_memory //= $old_memory;
944 $wanted_swap //= $old_swap;
945
946 my $total = $wanted_memory + $wanted_swap;
947 if ($running) {
948 my $old_total = $old_memory + $old_swap;
949 if ($total > $old_total) {
950 PVE::LXC::write_cgroup_value("memory", $vmid,
951 "memory.memsw.limit_in_bytes",
952 int($total*1024*1024));
953 PVE::LXC::write_cgroup_value("memory", $vmid,
954 "memory.limit_in_bytes",
955 int($wanted_memory*1024*1024));
956 } else {
957 PVE::LXC::write_cgroup_value("memory", $vmid,
958 "memory.limit_in_bytes",
959 int($wanted_memory*1024*1024));
960 PVE::LXC::write_cgroup_value("memory", $vmid,
961 "memory.memsw.limit_in_bytes",
962 int($total*1024*1024));
963 }
964 }
965 $conf->{memory} = $wanted_memory;
966 $conf->{swap} = $wanted_swap;
967
968 PVE::LXC::Config->write_config($vmid, $conf) if $running;
969 }
970
971 my $storecfg = PVE::Storage::config();
972
973 my $used_volids = {};
974 my $check_content_type = sub {
975 my ($mp) = @_;
976 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
977 my $storage_config = PVE::Storage::storage_config($storecfg, $sid);
978 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
979 if !$storage_config->{content}->{rootdir};
980 };
981
982 my $rescan_volume = sub {
983 my ($mp) = @_;
984 eval {
985 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
986 if !defined($mp->{size});
987 };
988 warn "Could not rescan volume size - $@\n" if $@;
989 };
990
991 foreach my $opt (keys %$param) {
992 my $value = $param->{$opt};
993 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
994 if ($opt eq 'hostname' || $opt eq 'arch') {
995 $conf->{$opt} = $value;
996 } elsif ($opt eq 'onboot') {
997 $conf->{$opt} = $value ? 1 : 0;
998 } elsif ($opt eq 'startup') {
999 $conf->{$opt} = $value;
1000 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1001 next if $hotplug_error->($opt);
1002 $conf->{$opt} = $value;
1003 } elsif ($opt eq 'nameserver') {
1004 next if $hotplug_error->($opt);
1005 my $list = PVE::LXC::verify_nameserver_list($value);
1006 $conf->{$opt} = $list;
1007 } elsif ($opt eq 'searchdomain') {
1008 next if $hotplug_error->($opt);
1009 my $list = PVE::LXC::verify_searchdomain_list($value);
1010 $conf->{$opt} = $list;
1011 } elsif ($opt eq 'cores') {
1012 $conf->{$opt} = $value;# rest is handled by pvestatd
1013 } elsif ($opt eq 'cpulimit') {
1014 if ($value == 0) {
1015 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1016 } else {
1017 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1018 }
1019 $conf->{$opt} = $value;
1020 } elsif ($opt eq 'cpuunits') {
1021 $conf->{$opt} = $value;
1022 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1023 } elsif ($opt eq 'description') {
1024 $conf->{$opt} = $value;
1025 } elsif ($opt =~ m/^net(\d+)$/) {
1026 my $netid = $1;
1027 my $net = PVE::LXC::Config->parse_lxc_network($value);
1028 if (!$running) {
1029 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
1030 } else {
1031 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1032 }
1033 } elsif ($opt eq 'protection') {
1034 $conf->{$opt} = $value ? 1 : 0;
1035 } elsif ($opt =~ m/^mp(\d+)$/) {
1036 next if $hotplug_error->($opt);
1037 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1038 my $old = $conf->{$opt};
1039 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
1040 if ($mp->{type} eq 'volume') {
1041 &$check_content_type($mp);
1042 $used_volids->{$mp->{volume}} = 1;
1043 &$rescan_volume($mp);
1044 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
1045 } else {
1046 $conf->{$opt} = $value;
1047 }
1048 if (defined($old)) {
1049 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
1050 if ($mp->{type} eq 'volume') {
1051 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1052 }
1053 }
1054 $new_disks = 1;
1055 } elsif ($opt eq 'rootfs') {
1056 next if $hotplug_error->($opt);
1057 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1058 my $old = $conf->{$opt};
1059 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
1060 if ($mp->{type} eq 'volume') {
1061 &$check_content_type($mp);
1062 $used_volids->{$mp->{volume}} = 1;
1063 &$rescan_volume($mp);
1064 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
1065 } else {
1066 $conf->{$opt} = $value;
1067 }
1068 if (defined($old)) {
1069 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
1070 if ($mp->{type} eq 'volume') {
1071 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1072 }
1073 }
1074 $new_disks = 1;
1075 } elsif ($opt eq 'unprivileged') {
1076 die "unable to modify read-only option: '$opt'\n";
1077 } elsif ($opt eq 'ostype') {
1078 next if $hotplug_error->($opt);
1079 $conf->{$opt} = $value;
1080 } elsif ($opt eq 'features') {
1081 next if $hotplug_error->($opt);
1082 $conf->{$opt} = $value;
1083 } else {
1084 die "implement me: $opt";
1085 }
1086
1087 PVE::LXC::Config->write_config($vmid, $conf) if $running;
1088 }
1089
1090 # Apply deletions and creations of new volumes
1091 if (@deleted_volumes) {
1092 my $storage_cfg = PVE::Storage::config();
1093 foreach my $volume (@deleted_volumes) {
1094 next if $used_volids->{$volume}; # could have been re-added, too
1095 # also check for references in snapshots
1096 next if $class->is_volume_in_use($conf, $volume, 1);
1097 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1098 }
1099 }
1100
1101 if ($new_disks) {
1102 my $storage_cfg = PVE::Storage::config();
1103 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
1104 }
1105
1106 # This should be the last thing we do here
1107 if ($running && scalar(@nohotplug)) {
1108 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1109 }
1110 }
1111
1112 sub check_type {
1113 my ($class, $key, $value) = @_;
1114
1115 die "unknown setting '$key'\n" if !$confdesc->{$key};
1116
1117 my $type = $confdesc->{$key}->{type};
1118
1119 if (!defined($value)) {
1120 die "got undefined value\n";
1121 }
1122
1123 if ($value =~ m/[\n\r]/) {
1124 die "property contains a line feed\n";
1125 }
1126
1127 if ($type eq 'boolean') {
1128 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1129 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1130 die "type check ('boolean') failed - got '$value'\n";
1131 } elsif ($type eq 'integer') {
1132 return int($1) if $value =~ m/^(\d+)$/;
1133 die "type check ('integer') failed - got '$value'\n";
1134 } elsif ($type eq 'number') {
1135 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1136 die "type check ('number') failed - got '$value'\n";
1137 } elsif ($type eq 'string') {
1138 if (my $fmt = $confdesc->{$key}->{format}) {
1139 PVE::JSONSchema::check_format($fmt, $value);
1140 return $value;
1141 }
1142 return $value;
1143 } else {
1144 die "internal error"
1145 }
1146 }
1147
1148
1149 # add JSON properties for create and set function
1150 sub json_config_properties {
1151 my ($class, $prop) = @_;
1152
1153 foreach my $opt (keys %$confdesc) {
1154 next if $opt eq 'parent' || $opt eq 'snaptime';
1155 next if $prop->{$opt};
1156 $prop->{$opt} = $confdesc->{$opt};
1157 }
1158
1159 return $prop;
1160 }
1161
1162 sub __parse_ct_mountpoint_full {
1163 my ($class, $desc, $data, $noerr) = @_;
1164
1165 $data //= '';
1166
1167 my $res;
1168 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1169 if ($@) {
1170 return undef if $noerr;
1171 die $@;
1172 }
1173
1174 if (defined(my $size = $res->{size})) {
1175 $size = PVE::JSONSchema::parse_size($size);
1176 if (!defined($size)) {
1177 return undef if $noerr;
1178 die "invalid size: $size\n";
1179 }
1180 $res->{size} = $size;
1181 }
1182
1183 $res->{type} = $class->classify_mountpoint($res->{volume});
1184
1185 return $res;
1186 };
1187
1188 sub parse_ct_rootfs {
1189 my ($class, $data, $noerr) = @_;
1190
1191 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1192
1193 $res->{mp} = '/' if defined($res);
1194
1195 return $res;
1196 }
1197
1198 sub parse_ct_mountpoint {
1199 my ($class, $data, $noerr) = @_;
1200
1201 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1202 }
1203
1204 sub print_ct_mountpoint {
1205 my ($class, $info, $nomp) = @_;
1206 my $skip = [ 'type' ];
1207 push @$skip, 'mp' if $nomp;
1208 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1209 }
1210
1211 sub print_lxc_network {
1212 my ($class, $net) = @_;
1213 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1214 }
1215
1216 sub parse_lxc_network {
1217 my ($class, $data) = @_;
1218
1219 my $res = {};
1220
1221 return $res if !$data;
1222
1223 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1224
1225 $res->{type} = 'veth';
1226 if (!$res->{hwaddr}) {
1227 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1228 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1229 }
1230
1231 return $res;
1232 }
1233
1234 sub parse_features {
1235 my ($class, $data) = @_;
1236 return {} if !$data;
1237 return PVE::JSONSchema::parse_property_string($features_desc, $data);
1238 }
1239
1240 sub option_exists {
1241 my ($class, $name) = @_;
1242
1243 return defined($confdesc->{$name});
1244 }
1245 # END JSON config code
1246
1247 sub classify_mountpoint {
1248 my ($class, $vol) = @_;
1249 if ($vol =~ m!^/!) {
1250 return 'device' if $vol =~ m!^/dev/!;
1251 return 'bind';
1252 }
1253 return 'volume';
1254 }
1255
1256 my $is_volume_in_use = sub {
1257 my ($class, $config, $volid) = @_;
1258 my $used = 0;
1259
1260 $class->foreach_mountpoint($config, sub {
1261 my ($ms, $mountpoint) = @_;
1262 return if $used;
1263 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1264 });
1265
1266 return $used;
1267 };
1268
1269 sub is_volume_in_use_by_snapshots {
1270 my ($class, $config, $volid) = @_;
1271
1272 if (my $snapshots = $config->{snapshots}) {
1273 foreach my $snap (keys %$snapshots) {
1274 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1275 }
1276 }
1277
1278 return 0;
1279 };
1280
1281 sub is_volume_in_use {
1282 my ($class, $config, $volid, $include_snapshots) = @_;
1283 return 1 if $is_volume_in_use->($class, $config, $volid);
1284 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1285 return 0;
1286 }
1287
1288 sub has_dev_console {
1289 my ($class, $conf) = @_;
1290
1291 return !(defined($conf->{console}) && !$conf->{console});
1292 }
1293
1294 sub has_lxc_entry {
1295 my ($class, $conf, $keyname) = @_;
1296
1297 if (my $lxcconf = $conf->{lxc}) {
1298 foreach my $entry (@$lxcconf) {
1299 my ($key, undef) = @$entry;
1300 return 1 if $key eq $keyname;
1301 }
1302 }
1303
1304 return 0;
1305 }
1306
1307 sub get_tty_count {
1308 my ($class, $conf) = @_;
1309
1310 return $conf->{tty} // $confdesc->{tty}->{default};
1311 }
1312
1313 sub get_cmode {
1314 my ($class, $conf) = @_;
1315
1316 return $conf->{cmode} // $confdesc->{cmode}->{default};
1317 }
1318
1319 sub mountpoint_names {
1320 my ($class, $reverse) = @_;
1321
1322 my @names = ('rootfs');
1323
1324 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1325 push @names, "mp$i";
1326 }
1327
1328 return $reverse ? reverse @names : @names;
1329 }
1330
1331 sub foreach_mountpoint_full {
1332 my ($class, $conf, $reverse, $func, @param) = @_;
1333
1334 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1335 foreach my $key (@$mps) {
1336 my $value = $conf->{$key};
1337 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1338 next if !defined($mountpoint);
1339
1340 &$func($key, $mountpoint, @param);
1341 }
1342 }
1343
1344 sub foreach_mountpoint {
1345 my ($class, $conf, $func, @param) = @_;
1346
1347 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1348 }
1349
1350 sub foreach_mountpoint_reverse {
1351 my ($class, $conf, $func, @param) = @_;
1352
1353 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1354 }
1355
1356 sub get_vm_volumes {
1357 my ($class, $conf, $excludes) = @_;
1358
1359 my $vollist = [];
1360
1361 $class->foreach_mountpoint($conf, sub {
1362 my ($ms, $mountpoint) = @_;
1363
1364 return if $excludes && $ms eq $excludes;
1365
1366 my $volid = $mountpoint->{volume};
1367 return if !$volid || $mountpoint->{type} ne 'volume';
1368
1369 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1370 return if !$sid;
1371
1372 push @$vollist, $volid;
1373 });
1374
1375 return $vollist;
1376 }
1377
1378 sub get_replicatable_volumes {
1379 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1380
1381 my $volhash = {};
1382
1383 my $test_volid = sub {
1384 my ($volid, $mountpoint) = @_;
1385
1386 return if !$volid;
1387
1388 my $mptype = $mountpoint->{type};
1389 my $replicate = $mountpoint->{replicate} // 1;
1390
1391 if ($mptype ne 'volume') {
1392 # skip bindmounts if replicate = 0 even for cleanup,
1393 # since bind mounts could not have been replicated ever
1394 return if !$replicate;
1395 die "unable to replicate mountpoint type '$mptype'\n";
1396 }
1397
1398 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1399 return if !$storeid;
1400
1401 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
1402 return if $scfg->{shared};
1403
1404 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1405 return if !$owner || ($owner != $vmid);
1406
1407 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1408
1409 return if !$cleanup && !$replicate;
1410
1411 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
1412 return if $cleanup || $noerr;
1413 die "missing replicate feature on volume '$volid'\n";
1414 }
1415
1416 $volhash->{$volid} = 1;
1417 };
1418
1419 $class->foreach_mountpoint($conf, sub {
1420 my ($ms, $mountpoint) = @_;
1421 $test_volid->($mountpoint->{volume}, $mountpoint);
1422 });
1423
1424 foreach my $snapname (keys %{$conf->{snapshots}}) {
1425 my $snap = $conf->{snapshots}->{$snapname};
1426 $class->foreach_mountpoint($snap, sub {
1427 my ($ms, $mountpoint) = @_;
1428 $test_volid->($mountpoint->{volume}, $mountpoint);
1429 });
1430 }
1431
1432 # add 'unusedX' volumes to volhash
1433 foreach my $key (keys %$conf) {
1434 if ($key =~ m/^unused/) {
1435 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1436 }
1437 }
1438
1439 return $volhash;
1440 }
1441
1442 1;