1 package PVE
::LXC
::Config
;
6 use Fcntl
qw(O_RDONLY);
8 use PVE
::AbstractConfig
;
9 use PVE
::Cluster
qw(cfs_register_file);
10 use PVE
::DataCenterConfig
;
11 use PVE
::GuestHelpers
;
13 use PVE
::JSONSchema
qw(get_standard_option);
18 use base
qw(PVE::AbstractConfig);
21 FIFREEZE
=> 0xc0045877,
25 my $nodename = PVE
::INotify
::nodename
();
26 my $lock_handles = {};
27 my $lockdir = "/run/lock/lxc";
29 mkdir "/etc/pve/nodes/$nodename/lxc";
30 my $MAX_MOUNT_POINTS = 256;
31 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
33 # BEGIN implemented abstract methods from PVE::AbstractConfig
39 sub __config_max_unused_disks
{
42 return $MAX_UNUSED_DISKS;
45 sub config_file_lock
{
46 my ($class, $vmid) = @_;
48 return "$lockdir/pve-config-${vmid}.lock";
52 my ($class, $vmid, $node) = @_;
54 $node = $nodename if !$node;
55 return "nodes/$node/lxc/$vmid.conf";
58 sub mountpoint_backup_enabled
{
59 my ($class, $mp_key, $mountpoint) = @_;
64 if ($mp_key eq 'rootfs') {
67 } elsif ($mountpoint->{type
} ne 'volume') {
69 $reason = 'not a volume';
70 } elsif ($mountpoint->{backup
}) {
77 return wantarray ?
($enabled, $reason) : $enabled;
81 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
85 if ($feature eq 'copy' || $feature eq 'clone') {
86 $opts = {'valid_target_formats' => ['raw', 'subvol']};
89 $class->foreach_volume($conf, sub {
90 my ($ms, $mountpoint) = @_;
92 return if $err; # skip further test
93 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
95 $err = 1 if !PVE
::Storage
::volume_has_feature
(
96 $storecfg, $feature, $mountpoint->{volume
}, $snapname, $running, $opts);
102 sub __snapshot_save_vmstate
{
103 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
104 die "implement me - snapshot_save_vmstate\n";
107 sub __snapshot_activate_storages
{
108 my ($class, $conf, $include_vmstate) = @_;
110 my $storecfg = PVE
::Storage
::config
();
111 my $opts = $include_vmstate ?
{ 'extra_keys' => ['vmstate'] } : {};
112 my $storage_hash = {};
114 $class->foreach_volume_full($conf, $opts, sub {
115 my ($vs, $mountpoint) = @_;
117 return if $mountpoint->{type
} ne 'volume';
119 my ($storeid) = PVE
::Storage
::parse_volume_id
($mountpoint->{volume
});
120 $storage_hash->{$storeid} = 1;
123 PVE
::Storage
::activate_storage_list
($storecfg, [ sort keys $storage_hash->%* ]);
126 sub __snapshot_check_running
{
127 my ($class, $vmid) = @_;
128 return PVE
::LXC
::check_running
($vmid);
131 sub __snapshot_check_freeze_needed
{
132 my ($class, $vmid, $config, $save_vmstate) = @_;
134 my $ret = $class->__snapshot_check_running($vmid);
138 # implements similar functionality to fsfreeze(8)
139 sub fsfreeze_mountpoint
{
140 my ($path, $thaw) = @_;
142 my $op = $thaw ?
'thaw' : 'freeze';
143 my $ioctl = $thaw ? FITHAW
: FIFREEZE
;
145 sysopen my $fd, $path, O_RDONLY
or die "failed to open $path: $!\n";
147 if (!ioctl($fd, $ioctl, 0)) {
151 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
154 sub __snapshot_freeze
{
155 my ($class, $vmid, $unfreeze) = @_;
157 my $conf = $class->load_config($vmid);
158 my $storagecfg = PVE
::Storage
::config
();
161 $class->foreach_volume($conf, sub {
162 my ($ms, $mountpoint) = @_;
164 return if $mountpoint->{type
} ne 'volume';
166 if (PVE
::Storage
::volume_snapshot_needs_fsfreeze
($storagecfg, $mountpoint->{volume
})) {
167 push @$freeze_mps, $mountpoint->{mp
};
171 my $freeze_mountpoints = sub {
174 return if scalar(@$freeze_mps) == 0;
176 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
178 for my $mp (@$freeze_mps) {
179 eval{ fsfreeze_mountpoint
("/proc/${pid}/root/${mp}", $thaw); };
185 eval { PVE
::LXC
::thaw
($vmid); };
187 $freeze_mountpoints->(1);
189 PVE
::LXC
::freeze
($vmid);
190 PVE
::LXC
::sync_container_namespace
($vmid);
191 $freeze_mountpoints->(0);
195 sub __snapshot_create_vol_snapshot
{
196 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
198 my $storecfg = PVE
::Storage
::config
();
200 return if $snapname eq 'vzdump' &&
201 !$class->mountpoint_backup_enabled($ms, $mountpoint);
203 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
206 sub __snapshot_delete_remove_drive
{
207 my ($class, $snap, $remove_drive) = @_;
209 if ($remove_drive eq 'vmstate') {
210 die "implement me - saving vmstate\n";
212 my $value = $snap->{$remove_drive};
213 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
214 delete $snap->{$remove_drive};
216 $class->add_unused_volume($snap, $mountpoint->{volume
})
217 if $mountpoint && ($mountpoint->{type
} eq 'volume');
221 sub __snapshot_delete_vmstate_file
{
222 my ($class, $snap, $force) = @_;
224 die "implement me - saving vmstate\n";
227 sub __snapshot_delete_vol_snapshot
{
228 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
230 return if $snapname eq 'vzdump' &&
231 !$class->mountpoint_backup_enabled($ms, $mountpoint);
233 my $storecfg = PVE
::Storage
::config
();
234 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
235 push @$unused, $mountpoint->{volume
};
238 sub __snapshot_rollback_vol_possible
{
239 my ($class, $mountpoint, $snapname, $blockers) = @_;
241 my $storecfg = PVE
::Storage
::config
();
242 PVE
::Storage
::volume_rollback_is_possible
(
244 $mountpoint->{volume
},
250 sub __snapshot_rollback_vol_rollback
{
251 my ($class, $mountpoint, $snapname) = @_;
253 my $storecfg = PVE
::Storage
::config
();
254 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
257 sub __snapshot_rollback_vm_stop
{
258 my ($class, $vmid) = @_;
260 PVE
::LXC
::vm_stop
($vmid, 1)
261 if $class->__snapshot_check_running($vmid);
264 sub __snapshot_rollback_vm_start
{
265 my ($class, $vmid, $vmstate, $data);
267 die "implement me - save vmstate\n";
270 sub __snapshot_rollback_get_unused
{
271 my ($class, $conf, $snap) = @_;
275 $class->foreach_volume($conf, sub {
276 my ($vs, $volume) = @_;
278 return if $volume->{type
} ne 'volume';
281 my $volid = $volume->{volume
};
283 $class->foreach_volume($snap, sub {
284 my ($ms, $mountpoint) = @_;
287 return if ($mountpoint->{type
} ne 'volume');
290 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
293 push @$unused, $volid if !$found;
299 # END implemented abstract methods from PVE::AbstractConfig
301 # BEGIN JSON config code
303 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
306 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
308 sub is_valid_mount_option
{
310 return $option =~ $valid_mount_option_re;
317 format
=> 'pve-lxc-mp-string',
318 format_description
=> 'volume',
319 description
=> 'Volume, device or directory to mount into the container.',
323 format
=> 'disk-size',
324 format_description
=> 'DiskSize',
325 description
=> 'Volume size (read only value).',
330 description
=> 'Explicitly enable or disable ACL support.',
336 description
=> 'Extra mount options for rootfs/mps.',
337 format_description
=> 'opt[;opt...]',
338 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
342 description
=> 'Read-only mount point',
347 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
352 description
=> 'Will include this volume to a storage replica job.',
358 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
359 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!",
365 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
366 type
=> 'string', format
=> $rootfs_desc,
367 description
=> "Use volume as container root.",
371 # IP address with optional interface suffix for link local ipv6 addresses
372 PVE
::JSONSchema
::register_format
('lxc-ip-with-ll-iface', \
&verify_ip_with_ll_iface
);
373 sub verify_ip_with_ll_iface
{
374 my ($addr, $noerr) = @_;
376 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
377 if (PVE
::JSONSchema
::pve_verify_ip
($addr, 1)
378 && PVE
::JSONSchema
::pve_verify_iface
($iface, 1))
384 return PVE
::JSONSchema
::pve_verify_ip
($addr, $noerr);
388 my $features_desc = {
392 description
=> "Allow mounting file systems of specific types."
393 ." This should be a list of file system types as used with the mount command."
394 ." Note that this can have negative effects on the container's security."
395 ." With access to a loop device, mounting a file can circumvent the mknod"
396 ." permission of the devices cgroup, mounting an NFS file system can"
397 ." block the host's I/O completely and prevent it from rebooting, etc.",
398 format_description
=> 'fstype;fstype;...',
399 pattern
=> qr/[a-zA-Z0-9_; ]+/,
405 description
=> "Allow nesting."
406 ." Best used with unprivileged containers with additional id mapping."
407 ." Note that this will expose procfs and sysfs contents of the host"
414 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
415 ." This is required to use docker inside a container."
416 ." By default unprivileged containers will see this system call as non-existent."
417 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
418 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
419 ." Essentially, you can choose between running systemd-networkd or docker.",
425 description
=> "Allow using 'fuse' file systems in a container."
426 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
432 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
433 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
434 ." This is experimental.",
440 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
441 ." This can break networking under newer (>= v245) systemd-network use."
449 description
=> "Lock/unlock the VM.",
450 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
455 description
=> "Specifies whether a VM will be started during system bootup.",
458 startup
=> get_standard_option
('pve-startup-order'),
462 description
=> "Enable/disable Template.",
468 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
469 description
=> "OS architecture type.",
475 enum
=> [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
476 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.",
481 description
=> "Attach a console device (/dev/console) to the container.",
487 description
=> "Specify the number of tty available to the container",
495 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
502 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.",
510 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.",
518 description
=> "Amount of RAM for the VM in MB.",
525 description
=> "Amount of SWAP for the VM in MB.",
531 description
=> "Set a host name for the container.",
532 type
=> 'string', format
=> 'dns-name',
538 description
=> "Description for the Container. Shown in the web-interface CT's summary."
539 ." This is saved as comment inside the configuration file.",
540 maxLength
=> 1024 * 8,
544 type
=> 'string', format
=> 'dns-name-list',
545 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
549 type
=> 'string', format
=> 'lxc-ip-with-ll-iface-list',
550 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.",
554 type
=> 'string', format
=> 'pve-ct-timezone',
555 description
=> "Time zone to use in the container. If option isn't set, then nothing will be done. Can be set to 'host' to match the host time zone, or an arbitrary time zone option from /usr/share/zoneinfo/zone.tab",
557 rootfs
=> get_standard_option
('pve-ct-rootfs'),
560 type
=> 'string', format
=> 'pve-configid',
562 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
566 description
=> "Timestamp for snapshots.",
572 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).",
574 enum
=> ['shell', 'console', 'tty'],
580 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
586 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
592 format
=> $features_desc,
593 description
=> "Allow containers access to advanced features.",
598 format
=> 'pve-volume-id',
599 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
602 type
=> 'string', format
=> 'pve-tag-list',
603 description
=> 'Tags of the Container. This is only meta information.',
609 description
=> "Try to be more verbose. For now this only enables debug log-level on start.",
614 my $valid_lxc_conf_keys = {
615 'lxc.apparmor.profile' => 1,
616 'lxc.apparmor.allow_incomplete' => 1,
617 'lxc.apparmor.allow_nesting' => 1,
618 'lxc.apparmor.raw' => 1,
619 'lxc.selinux.context' => 1,
623 'lxc.signal.halt' => 1,
624 'lxc.signal.reboot' => 1,
625 'lxc.signal.stop' => 1,
628 'lxc.console.logfile' => 1,
629 'lxc.console.path' => 1,
631 'lxc.devtty.dir' => 1,
632 'lxc.hook.autodev' => 1,
635 'lxc.mount.fstab' => 1,
636 'lxc.mount.entry' => 1,
637 'lxc.mount.auto' => 1,
638 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
639 'lxc.rootfs.mount' => 1,
640 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
641 ', please use mount point options in the "rootfs" key',
647 'lxc.seccomp.profile' => 1,
648 'lxc.seccomp.notify.proxy' => 1,
649 'lxc.seccomp.notify.cookie' => 1,
651 'lxc.hook.pre-start' => 1,
652 'lxc.hook.pre-mount' => 1,
653 'lxc.hook.mount' => 1,
654 'lxc.hook.start' => 1,
655 'lxc.hook.stop' => 1,
656 'lxc.hook.post-stop' => 1,
657 'lxc.hook.clone' => 1,
658 'lxc.hook.destroy' => 1,
659 'lxc.hook.version' => 1,
660 'lxc.log.level' => 1,
662 'lxc.start.auto' => 1,
663 'lxc.start.delay' => 1,
664 'lxc.start.order' => 1,
666 'lxc.environment' => 1,
668 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
669 'lxc.sysctl.fs.mqueue' => 1,
670 'lxc.sysctl.kernel.msgmax' => 1,
671 'lxc.sysctl.kernel.msgmnb' => 1,
672 'lxc.sysctl.kernel.msgmni' => 1,
673 'lxc.sysctl.kernel.sem' => 1,
674 'lxc.sysctl.kernel.shmall' => 1,
675 'lxc.sysctl.kernel.shmmax' => 1,
676 'lxc.sysctl.kernel.shmmni' => 1,
677 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
680 my $deprecated_lxc_conf_keys = {
681 # Deprecated (removed with lxc 3.0):
682 'lxc.aa_profile' => 'lxc.apparmor.profile',
683 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
684 'lxc.console' => 'lxc.console.path',
685 'lxc.devttydir' => 'lxc.tty.dir',
686 'lxc.haltsignal' => 'lxc.signal.halt',
687 'lxc.rebootsignal' => 'lxc.signal.reboot',
688 'lxc.stopsignal' => 'lxc.signal.stop',
689 'lxc.id_map' => 'lxc.idmap',
690 'lxc.init_cmd' => 'lxc.init.cmd',
691 'lxc.loglevel' => 'lxc.log.level',
692 'lxc.logfile' => 'lxc.log.file',
693 'lxc.mount' => 'lxc.mount.fstab',
694 'lxc.network.type' => 'lxc.net.INDEX.type',
695 'lxc.network.flags' => 'lxc.net.INDEX.flags',
696 'lxc.network.link' => 'lxc.net.INDEX.link',
697 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
698 'lxc.network.name' => 'lxc.net.INDEX.name',
699 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
700 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
701 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
702 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
703 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
704 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
705 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
706 'lxc.pts' => 'lxc.pty.max',
707 'lxc.se_context' => 'lxc.selinux.context',
708 'lxc.seccomp' => 'lxc.seccomp.profile',
709 'lxc.tty' => 'lxc.tty.max',
710 'lxc.utsname' => 'lxc.uts.name',
713 sub is_valid_lxc_conf_key
{
714 my ($vmid, $key) = @_;
715 if ($key =~ /^lxc\.limit\./) {
716 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
719 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
720 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
723 my $validity = $valid_lxc_conf_keys->{$key};
724 return $validity if defined($validity);
725 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
726 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
727 || $key =~ /^lxc\.net\./; # allow custom network definitions
731 our $netconf_desc = {
735 description
=> "Network interface type.",
740 format_description
=> 'string',
741 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
742 pattern
=> '[-_.\w\d]+',
746 format_description
=> 'bridge',
747 description
=> 'Bridge to attach the network device to.',
748 pattern
=> '[-_.\w\d]+',
751 hwaddr
=> get_standard_option
('mac-addr', {
752 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)',
756 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
757 minimum
=> 64, # minimum ethernet frame is 64 bytes
762 format
=> 'pve-ipv4-config',
763 format_description
=> '(IPv4/CIDR|dhcp|manual)',
764 description
=> 'IPv4 address in CIDR format.',
770 format_description
=> 'GatewayIPv4',
771 description
=> 'Default gateway for IPv4 traffic.',
776 format
=> 'pve-ipv6-config',
777 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
778 description
=> 'IPv6 address in CIDR format.',
784 format_description
=> 'GatewayIPv6',
785 description
=> 'Default gateway for IPv6 traffic.',
790 description
=> "Controls whether this interface's firewall rules should be used.",
797 description
=> "VLAN tag for this interface.",
802 pattern
=> qr/\d+(?:;\d+)*/,
803 format_description
=> 'vlanid[;vlanid...]',
804 description
=> "VLAN ids to pass through the interface",
809 format_description
=> 'mbps',
810 description
=> "Apply rate limiting to the interface",
814 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
816 my $MAX_LXC_NETWORKS = 32;
817 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
818 $confdesc->{"net$i"} = {
820 type
=> 'string', format
=> $netconf_desc,
821 description
=> "Specifies network interfaces for the container.",
825 PVE
::JSONSchema
::register_format
('pve-ct-timezone', \
&verify_ct_timezone
);
826 sub verify_ct_timezone
{
827 my ($timezone, $noerr) = @_;
829 return if $timezone eq 'host'; # using host settings
831 PVE
::JSONSchema
::pve_verify_timezone
($timezone);
834 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
835 sub verify_lxc_mp_string
{
836 my ($mp, $noerr) = @_;
840 # /. or /.. at the end
841 # ../ at the beginning
843 if($mp =~ m
@/\.\
.?
/@ ||
846 return undef if $noerr;
847 die "$mp contains illegal character sequences\n";
856 description
=> 'Whether to include the mount point in backups.',
857 verbose_description
=> 'Whether to include the mount point in backups '.
858 '(only used for volume mount points).',
863 format
=> 'pve-lxc-mp-string',
864 format_description
=> 'Path',
865 description
=> 'Path to the mount point as seen from inside the container '.
866 '(must not contain symlinks).',
867 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
868 "NOTE: Must not contain any symlinks for security reasons."
871 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
877 format
=> 'pve-volume-id',
878 format_description
=> 'volume',
879 description
=> 'The volume that is not used currently.',
883 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
884 $confdesc->{"mp$i"} = {
886 type
=> 'string', format
=> $mp_desc,
887 description
=> "Use volume as container mount point. Use the special " .
888 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
893 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
894 $confdesc->{"unused$i"} = {
896 type
=> 'string', format
=> $unused_desc,
897 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
901 sub parse_pct_config
{
902 my ($filename, $raw, $strict) = @_;
904 return undef if !defined($raw);
907 digest
=> Digest
::SHA
::sha1_hex
($raw),
912 my $handle_error = sub {
922 $filename =~ m
|/lxc/(\d
+).conf
$|
923 || die "got strange filename '$filename'";
931 my @lines = split(/\n/, $raw);
932 foreach my $line (@lines) {
933 next if $line =~ m/^\s*$/;
935 if ($line =~ m/^\[pve:pending\]\s*$/i) {
936 $section = 'pending';
937 $conf->{description
} = $descr if $descr;
939 $conf = $res->{$section} = {};
941 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
943 $conf->{description
} = $descr if $descr;
945 $conf = $res->{snapshots
}->{$section} = {};
949 if ($line =~ m/^\#(.*)$/) {
950 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
954 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
957 my $validity = is_valid_lxc_conf_key
($vmid, $key);
958 if ($validity eq 1) {
959 push @{$conf->{lxc
}}, [$key, $value];
960 } elsif (my $errmsg = $validity) {
961 $handle_error->("vm $vmid - $key: $errmsg\n");
963 $handle_error->("vm $vmid - unable to parse config: $line\n");
965 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
966 $descr .= PVE
::Tools
::decode_text
($2);
967 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
968 $conf->{snapstate
} = $1;
969 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
971 if ($section eq 'pending') {
972 $conf->{delete} = $value;
974 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
976 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
979 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
980 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
981 $conf->{$key} = $value;
983 $handle_error->("vm $vmid - unable to parse config: $line\n");
987 $conf->{description
} = $descr if $descr;
989 delete $res->{snapstate
}; # just to be sure
994 sub write_pct_config
{
995 my ($filename, $conf) = @_;
997 delete $conf->{snapstate
}; # just to be sure
999 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1000 my $used_volids = {};
1001 foreach my $vid (@$volidlist) {
1002 $used_volids->{$vid} = 1;
1005 # remove 'unusedX' settings if the volume is still used
1006 foreach my $key (keys %$conf) {
1007 my $value = $conf->{$key};
1008 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1009 delete $conf->{$key};
1013 my $generate_raw_config = sub {
1018 # add description as comment to top of file
1019 my $descr = $conf->{description
} || '';
1020 foreach my $cl (split(/\n/, $descr)) {
1021 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
1024 foreach my $key (sort keys %$conf) {
1025 next if $key eq 'digest' || $key eq 'description' ||
1026 $key eq 'pending' || $key eq 'snapshots' ||
1027 $key eq 'snapname' || $key eq 'lxc';
1028 my $value = $conf->{$key};
1029 die "detected invalid newline inside property '$key'\n"
1031 $raw .= "$key: $value\n";
1034 if (my $lxcconf = $conf->{lxc
}) {
1035 foreach my $entry (@$lxcconf) {
1036 my ($k, $v) = @$entry;
1044 my $raw = &$generate_raw_config($conf);
1046 if (scalar(keys %{$conf->{pending
}})){
1047 $raw .= "\n[pve:pending]\n";
1048 $raw .= &$generate_raw_config($conf->{pending
});
1051 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
1052 $raw .= "\n[$snapname]\n";
1053 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
1059 sub update_pct_config
{
1060 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1062 my $storage_cfg = PVE
::Storage
::config
();
1064 foreach my $opt (@$revert) {
1065 delete $conf->{pending
}->{$opt};
1066 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1069 # write updates to pending section
1070 my $modified = {}; # record modified options
1072 foreach my $opt (@$delete) {
1073 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1074 warn "cannot delete '$opt' - not set in current configuration!\n";
1077 $modified->{$opt} = 1;
1078 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1079 die "unable to delete required option '$opt'\n";
1080 } elsif ($opt =~ m/^unused(\d+)$/) {
1081 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1082 } elsif ($opt =~ m/^mp(\d+)$/) {
1083 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1084 } elsif ($opt eq 'unprivileged') {
1085 die "unable to delete read-only option: '$opt'\n";
1087 $class->add_to_pending_delete($conf, $opt);
1090 my $check_content_type = sub {
1092 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
1093 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
1094 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1095 if !$storage_config->{content
}->{rootdir
};
1098 foreach my $opt (sort keys %$param) { # add/change
1099 $modified->{$opt} = 1;
1100 my $value = $param->{$opt};
1101 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1102 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1103 my $mp = $class->parse_volume($opt, $value);
1104 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
1105 } elsif ($opt eq 'hookscript') {
1106 PVE
::GuestHelpers
::check_hookscript
($value);
1107 } elsif ($opt eq 'nameserver') {
1108 $value = PVE
::LXC
::verify_nameserver_list
($value);
1109 } elsif ($opt eq 'searchdomain') {
1110 $value = PVE
::LXC
::verify_searchdomain_list
($value);
1111 } elsif ($opt eq 'unprivileged') {
1112 die "unable to modify read-only option: '$opt'\n";
1114 $conf->{pending
}->{$opt} = $value;
1115 $class->remove_from_pending_delete($conf, $opt);
1118 my $changes = $class->cleanup_pending($conf);
1122 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1124 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1131 my ($class, $key, $value) = @_;
1133 die "unknown setting '$key'\n" if !$confdesc->{$key};
1135 my $type = $confdesc->{$key}->{type
};
1137 if (!defined($value)) {
1138 die "got undefined value\n";
1141 if ($value =~ m/[\n\r]/) {
1142 die "property contains a line feed\n";
1145 if ($type eq 'boolean') {
1146 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1147 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1148 die "type check ('boolean') failed - got '$value'\n";
1149 } elsif ($type eq 'integer') {
1150 return int($1) if $value =~ m/^(\d+)$/;
1151 die "type check ('integer') failed - got '$value'\n";
1152 } elsif ($type eq 'number') {
1153 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1154 die "type check ('number') failed - got '$value'\n";
1155 } elsif ($type eq 'string') {
1156 if (my $fmt = $confdesc->{$key}->{format
}) {
1157 PVE
::JSONSchema
::check_format
($fmt, $value);
1162 die "internal error"
1167 # add JSON properties for create and set function
1168 sub json_config_properties
{
1169 my ($class, $prop) = @_;
1171 foreach my $opt (keys %$confdesc) {
1172 next if $opt eq 'parent' || $opt eq 'snaptime';
1173 next if $prop->{$opt};
1174 $prop->{$opt} = $confdesc->{$opt};
1180 my $parse_ct_mountpoint_full = sub {
1181 my ($class, $desc, $data, $noerr) = @_;
1186 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1188 return undef if $noerr;
1192 if (defined(my $size = $res->{size
})) {
1193 $size = PVE
::JSONSchema
::parse_size
($size);
1194 if (!defined($size)) {
1195 return undef if $noerr;
1196 die "invalid size: $size\n";
1198 $res->{size
} = $size;
1201 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1206 sub print_ct_mountpoint
{
1207 my ($class, $info, $nomp) = @_;
1208 my $skip = [ 'type' ];
1209 push @$skip, 'mp' if $nomp;
1210 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1213 sub print_ct_unused
{
1214 my ($class, $info) = @_;
1216 my $skip = [ 'type' ];
1217 return PVE
::JSONSchema
::print_property_string
($info, $unused_desc, $skip);
1221 my ($class, $key, $volume_string, $noerr) = @_;
1223 if ($key eq 'rootfs') {
1224 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1225 $res->{mp
} = '/' if defined($res);
1227 } elsif ($key =~ m/^mp\d+$/) {
1228 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1229 } elsif ($key =~ m/^unused\d+$/) {
1230 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1233 die "parse_volume - unknown type: $key\n" if !$noerr;
1239 my ($class, $key, $volume) = @_;
1241 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1243 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1252 sub print_lxc_network
{
1253 my ($class, $net) = @_;
1254 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1257 sub parse_lxc_network
{
1258 my ($class, $data) = @_;
1260 return {} if !$data;
1262 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1264 $res->{type
} = 'veth';
1265 if (!$res->{hwaddr
}) {
1266 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1267 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1273 sub parse_features
{
1274 my ($class, $data) = @_;
1275 return {} if !$data;
1276 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1280 my ($class, $name) = @_;
1282 return defined($confdesc->{$name});
1284 # END JSON config code
1286 my $LXC_FASTPLUG_OPTIONS= {
1298 sub vmconfig_hotplug_pending
{
1299 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1301 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1302 my $rootdir = "/proc/$pid/root";
1304 my $add_hotplug_error = sub {
1305 my ($opt, $msg) = @_;
1306 $errors->{$opt} = "unable to hotplug $opt: $msg";
1309 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1310 next if $selection && !$selection->{$opt};
1311 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1312 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1316 my $cgroup = PVE
::LXC
::CGroup-
>new($vmid);
1318 # There's no separate swap size to configure, there's memory and "total"
1319 # memory (iow. memory+swap). This means we have to change them together.
1320 my $hotplug_memory_done;
1321 my $hotplug_memory = sub {
1322 my ($wanted_memory, $wanted_swap) = @_;
1324 $wanted_memory = int($wanted_memory * 1024 * 1024) if defined($wanted_memory);
1325 $wanted_swap = int($wanted_swap * 1024 * 1024) if defined($wanted_swap);
1326 $cgroup->change_memory_limit($wanted_memory, $wanted_swap);
1328 $hotplug_memory_done = 1;
1331 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1332 # FIXME: $force deletion is not implemented for CTs
1333 foreach my $opt (sort keys %$pending_delete_hash) {
1334 next if $selection && !$selection->{$opt};
1336 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1338 } elsif ($opt =~ m/^unused(\d+)$/) {
1339 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1340 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1341 } elsif ($opt eq 'swap') {
1342 $hotplug_memory->(undef, 0);
1343 } elsif ($opt eq 'cpulimit') {
1344 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1345 } elsif ($opt eq 'cpuunits') {
1346 $cgroup->change_cpu_shares(undef, $confdesc->{cpuunits
}->{default});
1347 } elsif ($opt =~ m/^net(\d)$/) {
1349 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1351 die "skip\n"; # skip non-hotpluggable opts
1355 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1357 delete $conf->{$opt};
1358 $class->remove_from_pending_delete($conf, $opt);
1362 foreach my $opt (sort keys %{$conf->{pending
}}) {
1363 next if $opt eq 'delete'; # just to be sure
1364 next if $selection && !$selection->{$opt};
1365 my $value = $conf->{pending
}->{$opt};
1367 if ($opt eq 'cpulimit') {
1368 my $quota = 100000 * $value;
1369 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1370 } elsif ($opt eq 'cpuunits') {
1371 $cgroup->change_cpu_shares($value, $confdesc->{cpuunits
}->{default});
1372 } elsif ($opt =~ m/^net(\d+)$/) {
1374 my $net = $class->parse_lxc_network($value);
1375 $value = $class->print_lxc_network($net);
1376 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1377 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1378 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1379 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1381 } elsif ($opt =~ m/^mp(\d+)$/) {
1382 if (!PVE
::LXC
::Tools
::can_use_new_mount_api
()) {
1386 if (exists($conf->{$opt})) {
1387 die "skip\n"; # don't try to hotplug over existing mp
1390 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1391 # apply_pending_mountpoint modifies the value if it creates a new disk
1392 $value = $conf->{pending
}->{$opt};
1394 die "skip\n"; # skip non-hotpluggable
1398 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1400 $conf->{$opt} = $value;
1401 delete $conf->{pending
}->{$opt};
1406 sub vmconfig_apply_pending
{
1407 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1409 my $add_apply_error = sub {
1410 my ($opt, $msg) = @_;
1411 my $err_msg = "unable to apply pending change $opt : $msg";
1412 $errors->{$opt} = $err_msg;
1416 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1417 # FIXME: $force deletion is not implemented for CTs
1418 foreach my $opt (sort keys %$pending_delete_hash) {
1419 next if $selection && !$selection->{$opt};
1421 if ($opt =~ m/^mp(\d+)$/) {
1422 my $mp = $class->parse_volume($opt, $conf->{$opt});
1423 if ($mp->{type
} eq 'volume') {
1424 $class->add_unused_volume($conf, $mp->{volume
})
1425 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1427 } elsif ($opt =~ m/^unused(\d+)$/) {
1428 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1429 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1433 $add_apply_error->($opt, $err);
1435 delete $conf->{$opt};
1436 $class->remove_from_pending_delete($conf, $opt);
1440 $class->cleanup_pending($conf);
1442 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1443 next if $opt eq 'delete'; # just to be sure
1444 next if $selection && !$selection->{$opt};
1446 if ($opt =~ m/^mp(\d+)$/) {
1447 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1448 } elsif ($opt =~ m/^net(\d+)$/) {
1450 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1451 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1455 $add_apply_error->($opt, $err);
1457 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1462 my $rescan_volume = sub {
1463 my ($storecfg, $mp) = @_;
1465 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1467 warn "Could not rescan volume size - $@\n" if $@;
1470 sub apply_pending_mountpoint
{
1471 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1473 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1474 my $old = $conf->{$opt};
1475 if ($mp->{type
} eq 'volume' && $mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1476 my $original_value = $conf->{pending
}->{$opt};
1477 my $vollist = PVE
::LXC
::create_disks
(
1480 { $opt => $original_value },
1485 # Re-parse mount point:
1486 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1488 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1492 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1493 # The pending-changes code collects errors but keeps on looping through further
1494 # pending changes, so unroll the change in $conf as well if destroy_disks()
1496 $conf->{pending
}->{$opt} = $original_value;
1501 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1502 $rescan_volume->($storecfg, $mp) if $mp->{type
} eq 'volume';
1504 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1506 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1509 if (defined($old)) {
1510 my $mp = $class->parse_volume($opt, $old);
1511 if ($mp->{type
} eq 'volume') {
1512 $class->add_unused_volume($conf, $mp->{volume
})
1513 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1518 sub classify_mountpoint
{
1519 my ($class, $vol) = @_;
1520 if ($vol =~ m!^/!) {
1521 return 'device' if $vol =~ m!^/dev/!;
1527 my $__is_volume_in_use = sub {
1528 my ($class, $config, $volid) = @_;
1531 $class->foreach_volume($config, sub {
1532 my ($ms, $mountpoint) = @_;
1534 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1540 sub is_volume_in_use_by_snapshots
{
1541 my ($class, $config, $volid) = @_;
1543 if (my $snapshots = $config->{snapshots
}) {
1544 foreach my $snap (keys %$snapshots) {
1545 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1552 sub is_volume_in_use
{
1553 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1554 return 1 if $__is_volume_in_use->($class, $config, $volid);
1555 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1556 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1560 sub has_dev_console
{
1561 my ($class, $conf) = @_;
1563 return !(defined($conf->{console
}) && !$conf->{console
});
1567 my ($class, $conf, $keyname) = @_;
1569 if (my $lxcconf = $conf->{lxc
}) {
1570 foreach my $entry (@$lxcconf) {
1571 my ($key, undef) = @$entry;
1572 return 1 if $key eq $keyname;
1580 my ($class, $conf) = @_;
1582 return $conf->{tty
} // $confdesc->{tty
}->{default};
1586 my ($class, $conf) = @_;
1588 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1591 sub valid_volume_keys
{
1592 my ($class, $reverse) = @_;
1594 my @names = ('rootfs');
1596 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1597 push @names, "mp$i";
1600 return $reverse ?
reverse @names : @names;
1603 sub valid_volume_keys_with_unused
{
1604 my ($class, $reverse) = @_;
1605 my @names = $class->valid_volume_keys();
1606 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1607 push @names, "unused$i";
1609 return $reverse ?
reverse @names : @names;
1612 sub get_vm_volumes
{
1613 my ($class, $conf, $excludes) = @_;
1617 $class->foreach_volume($conf, sub {
1618 my ($ms, $mountpoint) = @_;
1620 return if $excludes && $ms eq $excludes;
1622 my $volid = $mountpoint->{volume
};
1623 return if !$volid || $mountpoint->{type
} ne 'volume';
1625 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1628 push @$vollist, $volid;
1634 sub get_replicatable_volumes
{
1635 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1639 my $test_volid = sub {
1640 my ($volid, $mountpoint) = @_;
1644 my $mptype = $mountpoint->{type
};
1645 my $replicate = $mountpoint->{replicate
} // 1;
1647 if ($mptype ne 'volume') {
1648 # skip bindmounts if replicate = 0 even for cleanup,
1649 # since bind mounts could not have been replicated ever
1650 return if !$replicate;
1651 die "unable to replicate mountpoint type '$mptype'\n";
1654 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1655 return if !$storeid;
1657 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1658 return if $scfg->{shared
};
1660 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1661 return if !$owner || ($owner != $vmid);
1663 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1665 return if !$cleanup && !$replicate;
1667 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1668 return if $cleanup || $noerr;
1669 die "missing replicate feature on volume '$volid'\n";
1672 $volhash->{$volid} = 1;
1675 $class->foreach_volume($conf, sub {
1676 my ($ms, $mountpoint) = @_;
1677 $test_volid->($mountpoint->{volume
}, $mountpoint);
1680 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1681 my $snap = $conf->{snapshots
}->{$snapname};
1682 $class->foreach_volume($snap, sub {
1683 my ($ms, $mountpoint) = @_;
1684 $test_volid->($mountpoint->{volume
}, $mountpoint);
1688 # add 'unusedX' volumes to volhash
1689 foreach my $key (keys %$conf) {
1690 if ($key =~ m/^unused/) {
1691 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });
1698 sub get_backup_volumes
{
1699 my ($class, $conf) = @_;
1701 my $return_volumes = [];
1703 my $test_mountpoint = sub {
1704 my ($key, $volume) = @_;
1706 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1708 push @$return_volumes, {
1710 included
=> $included,
1712 volume_config
=> $volume,
1716 PVE
::LXC
::Config-
>foreach_volume($conf, $test_mountpoint);
1718 return $return_volumes;