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);
96 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
97 $mountpoint->{volume
},
98 $snapname, $running, $opts);
104 sub __snapshot_save_vmstate
{
105 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
106 die "implement me - snapshot_save_vmstate\n";
109 sub __snapshot_activate_storages
{
110 my ($class, $conf, $include_vmstate) = @_;
112 my $storecfg = PVE
::Storage
::config
();
113 my $opts = $include_vmstate ?
{ 'extra_keys' => ['vmstate'] } : {};
114 my $storage_hash = {};
116 $class->foreach_volume_full($conf, $opts, sub {
117 my ($vs, $mountpoint) = @_;
119 return if $mountpoint->{type
} ne 'volume';
121 my ($storeid) = PVE
::Storage
::parse_volume_id
($mountpoint->{volume
});
122 $storage_hash->{$storeid} = 1;
125 PVE
::Storage
::activate_storage_list
($storecfg, [ sort keys $storage_hash->%* ]);
128 sub __snapshot_check_running
{
129 my ($class, $vmid) = @_;
130 return PVE
::LXC
::check_running
($vmid);
133 sub __snapshot_check_freeze_needed
{
134 my ($class, $vmid, $config, $save_vmstate) = @_;
136 my $ret = $class->__snapshot_check_running($vmid);
140 # implements similar functionality to fsfreeze(8)
141 sub fsfreeze_mountpoint
{
142 my ($path, $thaw) = @_;
144 my $op = $thaw ?
'thaw' : 'freeze';
145 my $ioctl = $thaw ? FITHAW
: FIFREEZE
;
147 sysopen my $fd, $path, O_RDONLY
or die "failed to open $path: $!\n";
149 if (!ioctl($fd, $ioctl, 0)) {
153 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
156 sub __snapshot_freeze
{
157 my ($class, $vmid, $unfreeze) = @_;
159 my $conf = $class->load_config($vmid);
160 my $storagecfg = PVE
::Storage
::config
();
163 $class->foreach_volume($conf, sub {
164 my ($ms, $mountpoint) = @_;
166 return if $mountpoint->{type
} ne 'volume';
168 if (PVE
::Storage
::volume_snapshot_needs_fsfreeze
($storagecfg, $mountpoint->{volume
})) {
169 push @$freeze_mps, $mountpoint->{mp
};
173 my $freeze_mountpoints = sub {
176 return if scalar(@$freeze_mps) == 0;
178 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
180 for my $mp (@$freeze_mps) {
181 eval{ fsfreeze_mountpoint
("/proc/${pid}/root/${mp}", $thaw); };
187 eval { PVE
::LXC
::thaw
($vmid); };
189 $freeze_mountpoints->(1);
191 PVE
::LXC
::freeze
($vmid);
192 PVE
::LXC
::sync_container_namespace
($vmid);
193 $freeze_mountpoints->(0);
197 sub __snapshot_create_vol_snapshot
{
198 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
200 my $storecfg = PVE
::Storage
::config
();
202 return if $snapname eq 'vzdump' &&
203 !$class->mountpoint_backup_enabled($ms, $mountpoint);
205 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
208 sub __snapshot_delete_remove_drive
{
209 my ($class, $snap, $remove_drive) = @_;
211 if ($remove_drive eq 'vmstate') {
212 die "implement me - saving vmstate\n";
214 my $value = $snap->{$remove_drive};
215 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
216 delete $snap->{$remove_drive};
218 $class->add_unused_volume($snap, $mountpoint->{volume
})
219 if $mountpoint && ($mountpoint->{type
} eq 'volume');
223 sub __snapshot_delete_vmstate_file
{
224 my ($class, $snap, $force) = @_;
226 die "implement me - saving vmstate\n";
229 sub __snapshot_delete_vol_snapshot
{
230 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
232 return if $snapname eq 'vzdump' &&
233 !$class->mountpoint_backup_enabled($ms, $mountpoint);
235 my $storecfg = PVE
::Storage
::config
();
236 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
237 push @$unused, $mountpoint->{volume
};
240 sub __snapshot_rollback_vol_possible
{
241 my ($class, $mountpoint, $snapname, $blockers) = @_;
243 my $storecfg = PVE
::Storage
::config
();
244 PVE
::Storage
::volume_rollback_is_possible
(
246 $mountpoint->{volume
},
252 sub __snapshot_rollback_vol_rollback
{
253 my ($class, $mountpoint, $snapname) = @_;
255 my $storecfg = PVE
::Storage
::config
();
256 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
259 sub __snapshot_rollback_vm_stop
{
260 my ($class, $vmid) = @_;
262 PVE
::LXC
::vm_stop
($vmid, 1)
263 if $class->__snapshot_check_running($vmid);
266 sub __snapshot_rollback_vm_start
{
267 my ($class, $vmid, $vmstate, $data);
269 die "implement me - save vmstate\n";
272 sub __snapshot_rollback_get_unused
{
273 my ($class, $conf, $snap) = @_;
277 $class->foreach_volume($conf, sub {
278 my ($vs, $volume) = @_;
280 return if $volume->{type
} ne 'volume';
283 my $volid = $volume->{volume
};
285 $class->foreach_volume($snap, sub {
286 my ($ms, $mountpoint) = @_;
289 return if ($mountpoint->{type
} ne 'volume');
292 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
295 push @$unused, $volid if !$found;
301 # END implemented abstract methods from PVE::AbstractConfig
303 # BEGIN JSON config code
305 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
308 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
310 sub is_valid_mount_option
{
312 return $option =~ $valid_mount_option_re;
319 format
=> 'pve-lxc-mp-string',
320 format_description
=> 'volume',
321 description
=> 'Volume, device or directory to mount into the container.',
325 format
=> 'disk-size',
326 format_description
=> 'DiskSize',
327 description
=> 'Volume size (read only value).',
332 description
=> 'Explicitly enable or disable ACL support.',
338 description
=> 'Extra mount options for rootfs/mps.',
339 format_description
=> 'opt[;opt...]',
340 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
344 description
=> 'Read-only mount point',
349 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
354 description
=> 'Will include this volume to a storage replica job.',
360 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
361 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!",
367 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
368 type
=> 'string', format
=> $rootfs_desc,
369 description
=> "Use volume as container root.",
373 # IP address with optional interface suffix for link local ipv6 addresses
374 PVE
::JSONSchema
::register_format
('lxc-ip-with-ll-iface', \
&verify_ip_with_ll_iface
);
375 sub verify_ip_with_ll_iface
{
376 my ($addr, $noerr) = @_;
378 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
379 if (PVE
::JSONSchema
::pve_verify_ip
($addr, 1)
380 && PVE
::JSONSchema
::pve_verify_iface
($iface, 1))
386 return PVE
::JSONSchema
::pve_verify_ip
($addr, $noerr);
390 my $features_desc = {
394 description
=> "Allow mounting file systems of specific types."
395 ." This should be a list of file system types as used with the mount command."
396 ." Note that this can have negative effects on the container's security."
397 ." With access to a loop device, mounting a file can circumvent the mknod"
398 ." permission of the devices cgroup, mounting an NFS file system can"
399 ." block the host's I/O completely and prevent it from rebooting, etc.",
400 format_description
=> 'fstype;fstype;...',
401 pattern
=> qr/[a-zA-Z0-9_; ]+/,
407 description
=> "Allow nesting."
408 ." Best used with unprivileged containers with additional id mapping."
409 ." Note that this will expose procfs and sysfs contents of the host"
416 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
417 ." This is required to use docker inside a container."
418 ." By default unprivileged containers will see this system call as non-existent."
419 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
420 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
421 ." Essentially, you can choose between running systemd-networkd or docker.",
427 description
=> "Allow using 'fuse' file systems in a container."
428 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
434 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
435 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
436 ." This is experimental.",
442 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
443 ." This can break networking under newer (>= v245) systemd-network use."
451 description
=> "Lock/unlock the VM.",
452 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
457 description
=> "Specifies whether a VM will be started during system bootup.",
460 startup
=> get_standard_option
('pve-startup-order'),
464 description
=> "Enable/disable Template.",
470 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
471 description
=> "OS architecture type.",
477 enum
=> [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
478 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.",
483 description
=> "Attach a console device (/dev/console) to the container.",
489 description
=> "Specify the number of tty available to the container",
497 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
504 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.",
512 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.",
520 description
=> "Amount of RAM for the VM in MB.",
527 description
=> "Amount of SWAP for the VM in MB.",
533 description
=> "Set a host name for the container.",
534 type
=> 'string', format
=> 'dns-name',
540 description
=> "Description for the Container. Shown in the web-interface CT's summary."
541 ." This is saved as comment inside the configuration file.",
542 maxLength
=> 1024 * 8,
546 type
=> 'string', format
=> 'dns-name-list',
547 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
551 type
=> 'string', format
=> 'lxc-ip-with-ll-iface-list',
552 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.",
556 type
=> 'string', format
=> 'pve-ct-timezone',
557 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",
559 rootfs
=> get_standard_option
('pve-ct-rootfs'),
562 type
=> 'string', format
=> 'pve-configid',
564 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
568 description
=> "Timestamp for snapshots.",
574 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).",
576 enum
=> ['shell', 'console', 'tty'],
582 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
588 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
594 format
=> $features_desc,
595 description
=> "Allow containers access to advanced features.",
600 format
=> 'pve-volume-id',
601 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
604 type
=> 'string', format
=> 'pve-tag-list',
605 description
=> 'Tags of the Container. This is only meta information.',
611 description
=> "Try to be more verbose. For now this only enables debug log-level on start.",
616 my $valid_lxc_conf_keys = {
617 'lxc.apparmor.profile' => 1,
618 'lxc.apparmor.allow_incomplete' => 1,
619 'lxc.apparmor.allow_nesting' => 1,
620 'lxc.apparmor.raw' => 1,
621 'lxc.selinux.context' => 1,
625 'lxc.signal.halt' => 1,
626 'lxc.signal.reboot' => 1,
627 'lxc.signal.stop' => 1,
630 'lxc.console.logfile' => 1,
631 'lxc.console.path' => 1,
633 'lxc.devtty.dir' => 1,
634 'lxc.hook.autodev' => 1,
637 'lxc.mount.fstab' => 1,
638 'lxc.mount.entry' => 1,
639 'lxc.mount.auto' => 1,
640 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
641 'lxc.rootfs.mount' => 1,
642 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
643 ', please use mount point options in the "rootfs" key',
649 'lxc.seccomp.profile' => 1,
650 'lxc.seccomp.notify.proxy' => 1,
651 'lxc.seccomp.notify.cookie' => 1,
653 'lxc.hook.pre-start' => 1,
654 'lxc.hook.pre-mount' => 1,
655 'lxc.hook.mount' => 1,
656 'lxc.hook.start' => 1,
657 'lxc.hook.stop' => 1,
658 'lxc.hook.post-stop' => 1,
659 'lxc.hook.clone' => 1,
660 'lxc.hook.destroy' => 1,
661 'lxc.hook.version' => 1,
662 'lxc.log.level' => 1,
664 'lxc.start.auto' => 1,
665 'lxc.start.delay' => 1,
666 'lxc.start.order' => 1,
668 'lxc.environment' => 1,
670 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
671 'lxc.sysctl.fs.mqueue' => 1,
672 'lxc.sysctl.kernel.msgmax' => 1,
673 'lxc.sysctl.kernel.msgmnb' => 1,
674 'lxc.sysctl.kernel.msgmni' => 1,
675 'lxc.sysctl.kernel.sem' => 1,
676 'lxc.sysctl.kernel.shmall' => 1,
677 'lxc.sysctl.kernel.shmmax' => 1,
678 'lxc.sysctl.kernel.shmmni' => 1,
679 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
682 my $deprecated_lxc_conf_keys = {
683 # Deprecated (removed with lxc 3.0):
684 'lxc.aa_profile' => 'lxc.apparmor.profile',
685 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
686 'lxc.console' => 'lxc.console.path',
687 'lxc.devttydir' => 'lxc.tty.dir',
688 'lxc.haltsignal' => 'lxc.signal.halt',
689 'lxc.rebootsignal' => 'lxc.signal.reboot',
690 'lxc.stopsignal' => 'lxc.signal.stop',
691 'lxc.id_map' => 'lxc.idmap',
692 'lxc.init_cmd' => 'lxc.init.cmd',
693 'lxc.loglevel' => 'lxc.log.level',
694 'lxc.logfile' => 'lxc.log.file',
695 'lxc.mount' => 'lxc.mount.fstab',
696 'lxc.network.type' => 'lxc.net.INDEX.type',
697 'lxc.network.flags' => 'lxc.net.INDEX.flags',
698 'lxc.network.link' => 'lxc.net.INDEX.link',
699 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
700 'lxc.network.name' => 'lxc.net.INDEX.name',
701 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
702 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
703 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
704 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
705 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
706 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
707 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
708 'lxc.pts' => 'lxc.pty.max',
709 'lxc.se_context' => 'lxc.selinux.context',
710 'lxc.seccomp' => 'lxc.seccomp.profile',
711 'lxc.tty' => 'lxc.tty.max',
712 'lxc.utsname' => 'lxc.uts.name',
715 sub is_valid_lxc_conf_key
{
716 my ($vmid, $key) = @_;
717 if ($key =~ /^lxc\.limit\./) {
718 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
721 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
722 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
725 my $validity = $valid_lxc_conf_keys->{$key};
726 return $validity if defined($validity);
727 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
728 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
729 || $key =~ /^lxc\.net\./; # allow custom network definitions
733 our $netconf_desc = {
737 description
=> "Network interface type.",
742 format_description
=> 'string',
743 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
744 pattern
=> '[-_.\w\d]+',
748 format_description
=> 'bridge',
749 description
=> 'Bridge to attach the network device to.',
750 pattern
=> '[-_.\w\d]+',
753 hwaddr
=> get_standard_option
('mac-addr', {
754 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)',
758 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
759 minimum
=> 64, # minimum ethernet frame is 64 bytes
764 format
=> 'pve-ipv4-config',
765 format_description
=> '(IPv4/CIDR|dhcp|manual)',
766 description
=> 'IPv4 address in CIDR format.',
772 format_description
=> 'GatewayIPv4',
773 description
=> 'Default gateway for IPv4 traffic.',
778 format
=> 'pve-ipv6-config',
779 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
780 description
=> 'IPv6 address in CIDR format.',
786 format_description
=> 'GatewayIPv6',
787 description
=> 'Default gateway for IPv6 traffic.',
792 description
=> "Controls whether this interface's firewall rules should be used.",
799 description
=> "VLAN tag for this interface.",
804 pattern
=> qr/\d+(?:;\d+)*/,
805 format_description
=> 'vlanid[;vlanid...]',
806 description
=> "VLAN ids to pass through the interface",
811 format_description
=> 'mbps',
812 description
=> "Apply rate limiting to the interface",
816 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
818 my $MAX_LXC_NETWORKS = 32;
819 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
820 $confdesc->{"net$i"} = {
822 type
=> 'string', format
=> $netconf_desc,
823 description
=> "Specifies network interfaces for the container.",
827 PVE
::JSONSchema
::register_format
('pve-ct-timezone', \
&verify_ct_timezone
);
828 sub verify_ct_timezone
{
829 my ($timezone, $noerr) = @_;
831 return if $timezone eq 'host'; # using host settings
833 PVE
::JSONSchema
::pve_verify_timezone
($timezone);
836 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
837 sub verify_lxc_mp_string
{
838 my ($mp, $noerr) = @_;
842 # /. or /.. at the end
843 # ../ at the beginning
845 if($mp =~ m
@/\.\
.?
/@ ||
848 return undef if $noerr;
849 die "$mp contains illegal character sequences\n";
858 description
=> 'Whether to include the mount point in backups.',
859 verbose_description
=> 'Whether to include the mount point in backups '.
860 '(only used for volume mount points).',
865 format
=> 'pve-lxc-mp-string',
866 format_description
=> 'Path',
867 description
=> 'Path to the mount point as seen from inside the container '.
868 '(must not contain symlinks).',
869 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
870 "NOTE: Must not contain any symlinks for security reasons."
873 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
879 format
=> 'pve-volume-id',
880 format_description
=> 'volume',
881 description
=> 'The volume that is not used currently.',
885 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
886 $confdesc->{"mp$i"} = {
888 type
=> 'string', format
=> $mp_desc,
889 description
=> "Use volume as container mount point. Use the special " .
890 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
895 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
896 $confdesc->{"unused$i"} = {
898 type
=> 'string', format
=> $unused_desc,
899 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
903 sub parse_pct_config
{
904 my ($filename, $raw, $strict) = @_;
906 return undef if !defined($raw);
909 digest
=> Digest
::SHA
::sha1_hex
($raw),
914 my $handle_error = sub {
924 $filename =~ m
|/lxc/(\d
+).conf
$|
925 || die "got strange filename '$filename'";
933 my @lines = split(/\n/, $raw);
934 foreach my $line (@lines) {
935 next if $line =~ m/^\s*$/;
937 if ($line =~ m/^\[pve:pending\]\s*$/i) {
938 $section = 'pending';
939 $conf->{description
} = $descr if $descr;
941 $conf = $res->{$section} = {};
943 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
945 $conf->{description
} = $descr if $descr;
947 $conf = $res->{snapshots
}->{$section} = {};
951 if ($line =~ m/^\#(.*)$/) {
952 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
956 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
959 my $validity = is_valid_lxc_conf_key
($vmid, $key);
960 if ($validity eq 1) {
961 push @{$conf->{lxc
}}, [$key, $value];
962 } elsif (my $errmsg = $validity) {
963 $handle_error->("vm $vmid - $key: $errmsg\n");
965 $handle_error->("vm $vmid - unable to parse config: $line\n");
967 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
968 $descr .= PVE
::Tools
::decode_text
($2);
969 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
970 $conf->{snapstate
} = $1;
971 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
973 if ($section eq 'pending') {
974 $conf->{delete} = $value;
976 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
978 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
981 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
982 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
983 $conf->{$key} = $value;
985 $handle_error->("vm $vmid - unable to parse config: $line\n");
989 $conf->{description
} = $descr if $descr;
991 delete $res->{snapstate
}; # just to be sure
996 sub write_pct_config
{
997 my ($filename, $conf) = @_;
999 delete $conf->{snapstate
}; # just to be sure
1001 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1002 my $used_volids = {};
1003 foreach my $vid (@$volidlist) {
1004 $used_volids->{$vid} = 1;
1007 # remove 'unusedX' settings if the volume is still used
1008 foreach my $key (keys %$conf) {
1009 my $value = $conf->{$key};
1010 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1011 delete $conf->{$key};
1015 my $generate_raw_config = sub {
1020 # add description as comment to top of file
1021 my $descr = $conf->{description
} || '';
1022 foreach my $cl (split(/\n/, $descr)) {
1023 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
1026 foreach my $key (sort keys %$conf) {
1027 next if $key eq 'digest' || $key eq 'description' ||
1028 $key eq 'pending' || $key eq 'snapshots' ||
1029 $key eq 'snapname' || $key eq 'lxc';
1030 my $value = $conf->{$key};
1031 die "detected invalid newline inside property '$key'\n"
1033 $raw .= "$key: $value\n";
1036 if (my $lxcconf = $conf->{lxc
}) {
1037 foreach my $entry (@$lxcconf) {
1038 my ($k, $v) = @$entry;
1046 my $raw = &$generate_raw_config($conf);
1048 if (scalar(keys %{$conf->{pending
}})){
1049 $raw .= "\n[pve:pending]\n";
1050 $raw .= &$generate_raw_config($conf->{pending
});
1053 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
1054 $raw .= "\n[$snapname]\n";
1055 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
1061 sub update_pct_config
{
1062 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1064 my $storage_cfg = PVE
::Storage
::config
();
1066 foreach my $opt (@$revert) {
1067 delete $conf->{pending
}->{$opt};
1068 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1071 # write updates to pending section
1072 my $modified = {}; # record modified options
1074 foreach my $opt (@$delete) {
1075 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1076 warn "cannot delete '$opt' - not set in current configuration!\n";
1079 $modified->{$opt} = 1;
1080 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1081 die "unable to delete required option '$opt'\n";
1082 } elsif ($opt =~ m/^unused(\d+)$/) {
1083 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1084 } elsif ($opt =~ m/^mp(\d+)$/) {
1085 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1086 } elsif ($opt eq 'unprivileged') {
1087 die "unable to delete read-only option: '$opt'\n";
1089 $class->add_to_pending_delete($conf, $opt);
1092 my $check_content_type = sub {
1094 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
1095 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
1096 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1097 if !$storage_config->{content
}->{rootdir
};
1100 foreach my $opt (sort keys %$param) { # add/change
1101 $modified->{$opt} = 1;
1102 my $value = $param->{$opt};
1103 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1104 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1105 my $mp = $class->parse_volume($opt, $value);
1106 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
1107 } elsif ($opt eq 'hookscript') {
1108 PVE
::GuestHelpers
::check_hookscript
($value);
1109 } elsif ($opt eq 'nameserver') {
1110 $value = PVE
::LXC
::verify_nameserver_list
($value);
1111 } elsif ($opt eq 'searchdomain') {
1112 $value = PVE
::LXC
::verify_searchdomain_list
($value);
1113 } elsif ($opt eq 'unprivileged') {
1114 die "unable to modify read-only option: '$opt'\n";
1116 $conf->{pending
}->{$opt} = $value;
1117 $class->remove_from_pending_delete($conf, $opt);
1120 my $changes = $class->cleanup_pending($conf);
1124 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1126 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1133 my ($class, $key, $value) = @_;
1135 die "unknown setting '$key'\n" if !$confdesc->{$key};
1137 my $type = $confdesc->{$key}->{type
};
1139 if (!defined($value)) {
1140 die "got undefined value\n";
1143 if ($value =~ m/[\n\r]/) {
1144 die "property contains a line feed\n";
1147 if ($type eq 'boolean') {
1148 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1149 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1150 die "type check ('boolean') failed - got '$value'\n";
1151 } elsif ($type eq 'integer') {
1152 return int($1) if $value =~ m/^(\d+)$/;
1153 die "type check ('integer') failed - got '$value'\n";
1154 } elsif ($type eq 'number') {
1155 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1156 die "type check ('number') failed - got '$value'\n";
1157 } elsif ($type eq 'string') {
1158 if (my $fmt = $confdesc->{$key}->{format
}) {
1159 PVE
::JSONSchema
::check_format
($fmt, $value);
1164 die "internal error"
1169 # add JSON properties for create and set function
1170 sub json_config_properties
{
1171 my ($class, $prop) = @_;
1173 foreach my $opt (keys %$confdesc) {
1174 next if $opt eq 'parent' || $opt eq 'snaptime';
1175 next if $prop->{$opt};
1176 $prop->{$opt} = $confdesc->{$opt};
1182 my $parse_ct_mountpoint_full = sub {
1183 my ($class, $desc, $data, $noerr) = @_;
1188 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1190 return undef if $noerr;
1194 if (defined(my $size = $res->{size
})) {
1195 $size = PVE
::JSONSchema
::parse_size
($size);
1196 if (!defined($size)) {
1197 return undef if $noerr;
1198 die "invalid size: $size\n";
1200 $res->{size
} = $size;
1203 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1208 sub print_ct_mountpoint
{
1209 my ($class, $info, $nomp) = @_;
1210 my $skip = [ 'type' ];
1211 push @$skip, 'mp' if $nomp;
1212 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1215 sub print_ct_unused
{
1216 my ($class, $info) = @_;
1218 my $skip = [ 'type' ];
1219 return PVE
::JSONSchema
::print_property_string
($info, $unused_desc, $skip);
1223 my ($class, $key, $volume_string, $noerr) = @_;
1225 if ($key eq 'rootfs') {
1226 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1227 $res->{mp
} = '/' if defined($res);
1229 } elsif ($key =~ m/^mp\d+$/) {
1230 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1231 } elsif ($key =~ m/^unused\d+$/) {
1232 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1235 die "parse_volume - unknown type: $key\n" if !$noerr;
1241 my ($class, $key, $volume) = @_;
1243 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1245 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1254 sub print_lxc_network
{
1255 my ($class, $net) = @_;
1256 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1259 sub parse_lxc_network
{
1260 my ($class, $data) = @_;
1262 return {} if !$data;
1264 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1266 $res->{type
} = 'veth';
1267 if (!$res->{hwaddr
}) {
1268 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1269 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1275 sub parse_features
{
1276 my ($class, $data) = @_;
1277 return {} if !$data;
1278 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1282 my ($class, $name) = @_;
1284 return defined($confdesc->{$name});
1286 # END JSON config code
1288 my $LXC_FASTPLUG_OPTIONS= {
1300 sub vmconfig_hotplug_pending
{
1301 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1303 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1304 my $rootdir = "/proc/$pid/root";
1306 my $add_hotplug_error = sub {
1307 my ($opt, $msg) = @_;
1308 $errors->{$opt} = "unable to hotplug $opt: $msg";
1311 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1312 next if $selection && !$selection->{$opt};
1313 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1314 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1318 my $cgroup = PVE
::LXC
::CGroup-
>new($vmid);
1320 # There's no separate swap size to configure, there's memory and "total"
1321 # memory (iow. memory+swap). This means we have to change them together.
1322 my $hotplug_memory_done;
1323 my $hotplug_memory = sub {
1324 my ($wanted_memory, $wanted_swap) = @_;
1326 $wanted_memory = int($wanted_memory * 1024 * 1024) if defined($wanted_memory);
1327 $wanted_swap = int($wanted_swap * 1024 * 1024) if defined($wanted_swap);
1328 $cgroup->change_memory_limit($wanted_memory, $wanted_swap);
1330 $hotplug_memory_done = 1;
1333 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1334 # FIXME: $force deletion is not implemented for CTs
1335 foreach my $opt (sort keys %$pending_delete_hash) {
1336 next if $selection && !$selection->{$opt};
1338 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1340 } elsif ($opt =~ m/^unused(\d+)$/) {
1341 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1342 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1343 } elsif ($opt eq 'swap') {
1344 $hotplug_memory->(undef, 0);
1345 } elsif ($opt eq 'cpulimit') {
1346 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1347 } elsif ($opt eq 'cpuunits') {
1348 $cgroup->change_cpu_shares(undef, $confdesc->{cpuunits
}->{default});
1349 } elsif ($opt =~ m/^net(\d)$/) {
1351 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1353 die "skip\n"; # skip non-hotpluggable opts
1357 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1359 delete $conf->{$opt};
1360 $class->remove_from_pending_delete($conf, $opt);
1364 foreach my $opt (sort keys %{$conf->{pending
}}) {
1365 next if $opt eq 'delete'; # just to be sure
1366 next if $selection && !$selection->{$opt};
1367 my $value = $conf->{pending
}->{$opt};
1369 if ($opt eq 'cpulimit') {
1370 my $quota = 100000 * $value;
1371 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1372 } elsif ($opt eq 'cpuunits') {
1373 $cgroup->change_cpu_shares($value, $confdesc->{cpuunits
}->{default});
1374 } elsif ($opt =~ m/^net(\d+)$/) {
1376 my $net = $class->parse_lxc_network($value);
1377 $value = $class->print_lxc_network($net);
1378 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1379 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1380 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1381 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1383 } elsif ($opt =~ m/^mp(\d+)$/) {
1384 if (!PVE
::LXC
::Tools
::can_use_new_mount_api
()) {
1388 if (exists($conf->{$opt})) {
1389 die "skip\n"; # don't try to hotplug over existing mp
1392 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1393 # apply_pending_mountpoint modifies the value if it creates a new disk
1394 $value = $conf->{pending
}->{$opt};
1396 die "skip\n"; # skip non-hotpluggable
1400 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1402 $conf->{$opt} = $value;
1403 delete $conf->{pending
}->{$opt};
1408 sub vmconfig_apply_pending
{
1409 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1411 my $add_apply_error = sub {
1412 my ($opt, $msg) = @_;
1413 my $err_msg = "unable to apply pending change $opt : $msg";
1414 $errors->{$opt} = $err_msg;
1418 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1419 # FIXME: $force deletion is not implemented for CTs
1420 foreach my $opt (sort keys %$pending_delete_hash) {
1421 next if $selection && !$selection->{$opt};
1423 if ($opt =~ m/^mp(\d+)$/) {
1424 my $mp = $class->parse_volume($opt, $conf->{$opt});
1425 if ($mp->{type
} eq 'volume') {
1426 $class->add_unused_volume($conf, $mp->{volume
})
1427 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1429 } elsif ($opt =~ m/^unused(\d+)$/) {
1430 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1431 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1435 $add_apply_error->($opt, $err);
1437 delete $conf->{$opt};
1438 $class->remove_from_pending_delete($conf, $opt);
1442 $class->cleanup_pending($conf);
1444 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1445 next if $opt eq 'delete'; # just to be sure
1446 next if $selection && !$selection->{$opt};
1448 if ($opt =~ m/^mp(\d+)$/) {
1449 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1450 } elsif ($opt =~ m/^net(\d+)$/) {
1452 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1453 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1457 $add_apply_error->($opt, $err);
1459 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1464 my $rescan_volume = sub {
1465 my ($storecfg, $mp) = @_;
1467 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1469 warn "Could not rescan volume size - $@\n" if $@;
1472 sub apply_pending_mountpoint
{
1473 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1475 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1476 my $old = $conf->{$opt};
1477 if ($mp->{type
} eq 'volume' && $mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1478 my $original_value = $conf->{pending
}->{$opt};
1479 my $vollist = PVE
::LXC
::create_disks
(
1482 { $opt => $original_value },
1487 # Re-parse mount point:
1488 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1490 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1494 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1495 # The pending-changes code collects errors but keeps on looping through further
1496 # pending changes, so unroll the change in $conf as well if destroy_disks()
1498 $conf->{pending
}->{$opt} = $original_value;
1503 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1504 $rescan_volume->($storecfg, $mp) if $mp->{type
} eq 'volume';
1506 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1508 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1511 if (defined($old)) {
1512 my $mp = $class->parse_volume($opt, $old);
1513 if ($mp->{type
} eq 'volume') {
1514 $class->add_unused_volume($conf, $mp->{volume
})
1515 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1520 sub classify_mountpoint
{
1521 my ($class, $vol) = @_;
1522 if ($vol =~ m!^/!) {
1523 return 'device' if $vol =~ m!^/dev/!;
1529 my $__is_volume_in_use = sub {
1530 my ($class, $config, $volid) = @_;
1533 $class->foreach_volume($config, sub {
1534 my ($ms, $mountpoint) = @_;
1536 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1542 sub is_volume_in_use_by_snapshots
{
1543 my ($class, $config, $volid) = @_;
1545 if (my $snapshots = $config->{snapshots
}) {
1546 foreach my $snap (keys %$snapshots) {
1547 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1554 sub is_volume_in_use
{
1555 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1556 return 1 if $__is_volume_in_use->($class, $config, $volid);
1557 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1558 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1562 sub has_dev_console
{
1563 my ($class, $conf) = @_;
1565 return !(defined($conf->{console
}) && !$conf->{console
});
1569 my ($class, $conf, $keyname) = @_;
1571 if (my $lxcconf = $conf->{lxc
}) {
1572 foreach my $entry (@$lxcconf) {
1573 my ($key, undef) = @$entry;
1574 return 1 if $key eq $keyname;
1582 my ($class, $conf) = @_;
1584 return $conf->{tty
} // $confdesc->{tty
}->{default};
1588 my ($class, $conf) = @_;
1590 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1593 sub valid_volume_keys
{
1594 my ($class, $reverse) = @_;
1596 my @names = ('rootfs');
1598 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1599 push @names, "mp$i";
1602 return $reverse ?
reverse @names : @names;
1605 sub valid_volume_keys_with_unused
{
1606 my ($class, $reverse) = @_;
1607 my @names = $class->valid_volume_keys();
1608 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1609 push @names, "unused$i";
1611 return $reverse ?
reverse @names : @names;
1614 sub get_vm_volumes
{
1615 my ($class, $conf, $excludes) = @_;
1619 $class->foreach_volume($conf, sub {
1620 my ($ms, $mountpoint) = @_;
1622 return if $excludes && $ms eq $excludes;
1624 my $volid = $mountpoint->{volume
};
1625 return if !$volid || $mountpoint->{type
} ne 'volume';
1627 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1630 push @$vollist, $volid;
1636 sub get_replicatable_volumes
{
1637 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1641 my $test_volid = sub {
1642 my ($volid, $mountpoint) = @_;
1646 my $mptype = $mountpoint->{type
};
1647 my $replicate = $mountpoint->{replicate
} // 1;
1649 if ($mptype ne 'volume') {
1650 # skip bindmounts if replicate = 0 even for cleanup,
1651 # since bind mounts could not have been replicated ever
1652 return if !$replicate;
1653 die "unable to replicate mountpoint type '$mptype'\n";
1656 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1657 return if !$storeid;
1659 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1660 return if $scfg->{shared
};
1662 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1663 return if !$owner || ($owner != $vmid);
1665 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1667 return if !$cleanup && !$replicate;
1669 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1670 return if $cleanup || $noerr;
1671 die "missing replicate feature on volume '$volid'\n";
1674 $volhash->{$volid} = 1;
1677 $class->foreach_volume($conf, sub {
1678 my ($ms, $mountpoint) = @_;
1679 $test_volid->($mountpoint->{volume
}, $mountpoint);
1682 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1683 my $snap = $conf->{snapshots
}->{$snapname};
1684 $class->foreach_volume($snap, sub {
1685 my ($ms, $mountpoint) = @_;
1686 $test_volid->($mountpoint->{volume
}, $mountpoint);
1690 # add 'unusedX' volumes to volhash
1691 foreach my $key (keys %$conf) {
1692 if ($key =~ m/^unused/) {
1693 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });
1700 sub get_backup_volumes
{
1701 my ($class, $conf) = @_;
1703 my $return_volumes = [];
1705 my $test_mountpoint = sub {
1706 my ($key, $volume) = @_;
1708 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1710 push @$return_volumes, {
1712 included
=> $included,
1714 volume_config
=> $volume,
1718 PVE
::LXC
::Config-
>foreach_volume($conf, $test_mountpoint);
1720 return $return_volumes;