1 package PVE
::LXC
::Config
;
5 use Fcntl
qw(O_RDONLY);
7 use PVE
::AbstractConfig
;
8 use PVE
::Cluster
qw(cfs_register_file);
9 use PVE
::DataCenterConfig
;
10 use PVE
::GuestHelpers
;
12 use PVE
::JSONSchema
qw(get_standard_option);
17 use base
qw(PVE::AbstractConfig);
19 use constant {FIFREEZE
=> 0xc0045877,
20 FITHAW
=> 0xc0045878};
22 my $nodename = PVE
::INotify
::nodename
();
23 my $lock_handles = {};
24 my $lockdir = "/run/lock/lxc";
26 mkdir "/etc/pve/nodes/$nodename/lxc";
27 my $MAX_MOUNT_POINTS = 256;
28 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
30 # BEGIN implemented abstract methods from PVE::AbstractConfig
36 sub __config_max_unused_disks
{
39 return $MAX_UNUSED_DISKS;
42 sub config_file_lock
{
43 my ($class, $vmid) = @_;
45 return "$lockdir/pve-config-${vmid}.lock";
49 my ($class, $vmid, $node) = @_;
51 $node = $nodename if !$node;
52 return "nodes/$node/lxc/$vmid.conf";
55 sub mountpoint_backup_enabled
{
56 my ($class, $mp_key, $mountpoint) = @_;
61 if ($mp_key eq 'rootfs') {
64 } elsif ($mountpoint->{type
} ne 'volume') {
66 $reason = 'not a volume';
67 } elsif ($mountpoint->{backup
}) {
74 return wantarray ?
($enabled, $reason) : $enabled;
78 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
82 if ($feature eq 'copy' || $feature eq 'clone') {
83 $opts = {'valid_target_formats' => ['raw', 'subvol']};
86 $class->foreach_volume($conf, sub {
87 my ($ms, $mountpoint) = @_;
89 return if $err; # skip further test
90 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
93 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
94 $mountpoint->{volume
},
95 $snapname, $running, $opts);
101 sub __snapshot_save_vmstate
{
102 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
103 die "implement me - snapshot_save_vmstate\n";
106 sub __snapshot_activate_storages
{
107 my ($class, $conf, $include_vmstate) = @_;
109 my $storecfg = PVE
::Storage
::config
();
110 my $opts = $include_vmstate ?
{ 'extra_keys' => ['vmstate'] } : {};
111 my $storage_hash = {};
113 $class->foreach_volume_full($conf, $opts, sub {
114 my ($vs, $mountpoint) = @_;
116 return if $mountpoint->{type
} ne 'volume';
118 my ($storeid) = PVE
::Storage
::parse_volume_id
($mountpoint->{volume
});
119 $storage_hash->{$storeid} = 1;
122 PVE
::Storage
::activate_storage_list
($storecfg, [ sort keys $storage_hash->%* ]);
125 sub __snapshot_check_running
{
126 my ($class, $vmid) = @_;
127 return PVE
::LXC
::check_running
($vmid);
130 sub __snapshot_check_freeze_needed
{
131 my ($class, $vmid, $config, $save_vmstate) = @_;
133 my $ret = $class->__snapshot_check_running($vmid);
137 # implements similar functionality to fsfreeze(8)
138 sub fsfreeze_mountpoint
{
139 my ($path, $thaw) = @_;
141 my $op = $thaw ?
'thaw' : 'freeze';
142 my $ioctl = $thaw ? FITHAW
: FIFREEZE
;
144 sysopen my $fd, $path, O_RDONLY
or die "failed to open $path: $!\n";
146 if (!ioctl($fd, $ioctl, 0)) {
150 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
153 sub __snapshot_freeze
{
154 my ($class, $vmid, $unfreeze) = @_;
156 my $conf = $class->load_config($vmid);
157 my $storagecfg = PVE
::Storage
::config
();
160 $class->foreach_volume($conf, sub {
161 my ($ms, $mountpoint) = @_;
163 return if $mountpoint->{type
} ne 'volume';
165 if (PVE
::Storage
::volume_snapshot_needs_fsfreeze
($storagecfg, $mountpoint->{volume
})) {
166 push @$freeze_mps, $mountpoint->{mp
};
170 my $freeze_mountpoints = sub {
173 return if scalar(@$freeze_mps) == 0;
175 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
177 for my $mp (@$freeze_mps) {
178 eval{ fsfreeze_mountpoint
("/proc/${pid}/root/${mp}", $thaw); };
184 eval { PVE
::LXC
::thaw
($vmid); };
186 $freeze_mountpoints->(1);
188 PVE
::LXC
::freeze
($vmid);
189 PVE
::LXC
::sync_container_namespace
($vmid);
190 $freeze_mountpoints->(0);
194 sub __snapshot_create_vol_snapshot
{
195 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
197 my $storecfg = PVE
::Storage
::config
();
199 return if $snapname eq 'vzdump' &&
200 !$class->mountpoint_backup_enabled($ms, $mountpoint);
202 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
205 sub __snapshot_delete_remove_drive
{
206 my ($class, $snap, $remove_drive) = @_;
208 if ($remove_drive eq 'vmstate') {
209 die "implement me - saving vmstate\n";
211 my $value = $snap->{$remove_drive};
212 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
213 delete $snap->{$remove_drive};
215 $class->add_unused_volume($snap, $mountpoint->{volume
})
216 if $mountpoint && ($mountpoint->{type
} eq 'volume');
220 sub __snapshot_delete_vmstate_file
{
221 my ($class, $snap, $force) = @_;
223 die "implement me - saving vmstate\n";
226 sub __snapshot_delete_vol_snapshot
{
227 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
229 return if $snapname eq 'vzdump' &&
230 !$class->mountpoint_backup_enabled($ms, $mountpoint);
232 my $storecfg = PVE
::Storage
::config
();
233 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
234 push @$unused, $mountpoint->{volume
};
237 sub __snapshot_rollback_vol_possible
{
238 my ($class, $mountpoint, $snapname, $blockers) = @_;
240 my $storecfg = PVE
::Storage
::config
();
241 PVE
::Storage
::volume_rollback_is_possible
(
243 $mountpoint->{volume
},
249 sub __snapshot_rollback_vol_rollback
{
250 my ($class, $mountpoint, $snapname) = @_;
252 my $storecfg = PVE
::Storage
::config
();
253 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
256 sub __snapshot_rollback_vm_stop
{
257 my ($class, $vmid) = @_;
259 PVE
::LXC
::vm_stop
($vmid, 1)
260 if $class->__snapshot_check_running($vmid);
263 sub __snapshot_rollback_vm_start
{
264 my ($class, $vmid, $vmstate, $data);
266 die "implement me - save vmstate\n";
269 sub __snapshot_rollback_get_unused
{
270 my ($class, $conf, $snap) = @_;
274 $class->foreach_volume($conf, sub {
275 my ($vs, $volume) = @_;
277 return if $volume->{type
} ne 'volume';
280 my $volid = $volume->{volume
};
282 $class->foreach_volume($snap, sub {
283 my ($ms, $mountpoint) = @_;
286 return if ($mountpoint->{type
} ne 'volume');
289 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
292 push @$unused, $volid if !$found;
298 # END implemented abstract methods from PVE::AbstractConfig
300 # BEGIN JSON config code
302 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
305 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
307 sub is_valid_mount_option
{
309 return $option =~ $valid_mount_option_re;
316 format
=> 'pve-lxc-mp-string',
317 format_description
=> 'volume',
318 description
=> 'Volume, device or directory to mount into the container.',
322 format
=> 'disk-size',
323 format_description
=> 'DiskSize',
324 description
=> 'Volume size (read only value).',
329 description
=> 'Explicitly enable or disable ACL support.',
335 description
=> 'Extra mount options for rootfs/mps.',
336 format_description
=> 'opt[;opt...]',
337 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
341 description
=> 'Read-only mount point',
346 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
351 description
=> 'Will include this volume to a storage replica job.',
357 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
358 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!",
364 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
365 type
=> 'string', format
=> $rootfs_desc,
366 description
=> "Use volume as container root.",
370 # IP address with optional interface suffix for link local ipv6 addresses
371 PVE
::JSONSchema
::register_format
('lxc-ip-with-ll-iface', \
&verify_ip_with_ll_iface
);
372 sub verify_ip_with_ll_iface
{
373 my ($addr, $noerr) = @_;
375 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
376 if (PVE
::JSONSchema
::pve_verify_ip
($addr, 1)
377 && PVE
::JSONSchema
::pve_verify_iface
($iface, 1))
383 return PVE
::JSONSchema
::pve_verify_ip
($addr, $noerr);
387 my $features_desc = {
391 description
=> "Allow mounting file systems of specific types."
392 ." This should be a list of file system types as used with the mount command."
393 ." Note that this can have negative effects on the container's security."
394 ." With access to a loop device, mounting a file can circumvent the mknod"
395 ." permission of the devices cgroup, mounting an NFS file system can"
396 ." block the host's I/O completely and prevent it from rebooting, etc.",
397 format_description
=> 'fstype;fstype;...',
398 pattern
=> qr/[a-zA-Z0-9_; ]+/,
404 description
=> "Allow nesting."
405 ." Best used with unprivileged containers with additional id mapping."
406 ." Note that this will expose procfs and sysfs contents of the host"
413 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
414 ." This is required to use docker inside a container."
415 ." By default unprivileged containers will see this system call as non-existent."
416 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
417 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
418 ." Essentially, you can choose between running systemd-networkd or docker.",
424 description
=> "Allow using 'fuse' file systems in a container."
425 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
431 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
432 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
433 ." This is experimental.",
439 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
440 ." This can break networking under newer (>= v245) systemd-network use."
448 description
=> "Lock/unlock the VM.",
449 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
454 description
=> "Specifies whether a VM will be started during system bootup.",
457 startup
=> get_standard_option
('pve-startup-order'),
461 description
=> "Enable/disable Template.",
467 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
468 description
=> "OS architecture type.",
474 enum
=> [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
475 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.",
480 description
=> "Attach a console device (/dev/console) to the container.",
486 description
=> "Specify the number of tty available to the container",
494 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
501 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.",
509 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.",
517 description
=> "Amount of RAM for the VM in MB.",
524 description
=> "Amount of SWAP for the VM in MB.",
530 description
=> "Set a host name for the container.",
531 type
=> 'string', format
=> 'dns-name',
537 description
=> "Description for the Container. Shown in the web-interface CT's summary."
538 ." This is saved as comment inside the configuration file.",
539 maxLength
=> 1024 * 8,
543 type
=> 'string', format
=> 'dns-name-list',
544 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
548 type
=> 'string', format
=> 'lxc-ip-with-ll-iface-list',
549 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.",
553 type
=> 'string', format
=> 'pve-ct-timezone',
554 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",
556 rootfs
=> get_standard_option
('pve-ct-rootfs'),
559 type
=> 'string', format
=> 'pve-configid',
561 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
565 description
=> "Timestamp for snapshots.",
571 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).",
573 enum
=> ['shell', 'console', 'tty'],
579 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
585 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
591 format
=> $features_desc,
592 description
=> "Allow containers access to advanced features.",
597 format
=> 'pve-volume-id',
598 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
601 type
=> 'string', format
=> 'pve-tag-list',
602 description
=> 'Tags of the Container. This is only meta information.',
608 description
=> "Try to be more verbose. For now this only enables debug log-level on start.",
613 my $valid_lxc_conf_keys = {
614 'lxc.apparmor.profile' => 1,
615 'lxc.apparmor.allow_incomplete' => 1,
616 'lxc.apparmor.allow_nesting' => 1,
617 'lxc.apparmor.raw' => 1,
618 'lxc.selinux.context' => 1,
622 'lxc.signal.halt' => 1,
623 'lxc.signal.reboot' => 1,
624 'lxc.signal.stop' => 1,
627 'lxc.console.logfile' => 1,
628 'lxc.console.path' => 1,
630 'lxc.devtty.dir' => 1,
631 'lxc.hook.autodev' => 1,
634 'lxc.mount.fstab' => 1,
635 'lxc.mount.entry' => 1,
636 'lxc.mount.auto' => 1,
637 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
638 'lxc.rootfs.mount' => 1,
639 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
640 ', please use mount point options in the "rootfs" key',
646 'lxc.seccomp.profile' => 1,
647 'lxc.seccomp.notify.proxy' => 1,
648 'lxc.seccomp.notify.cookie' => 1,
650 'lxc.hook.pre-start' => 1,
651 'lxc.hook.pre-mount' => 1,
652 'lxc.hook.mount' => 1,
653 'lxc.hook.start' => 1,
654 'lxc.hook.stop' => 1,
655 'lxc.hook.post-stop' => 1,
656 'lxc.hook.clone' => 1,
657 'lxc.hook.destroy' => 1,
658 'lxc.hook.version' => 1,
659 'lxc.log.level' => 1,
661 'lxc.start.auto' => 1,
662 'lxc.start.delay' => 1,
663 'lxc.start.order' => 1,
665 'lxc.environment' => 1,
667 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
668 'lxc.sysctl.fs.mqueue' => 1,
669 'lxc.sysctl.kernel.msgmax' => 1,
670 'lxc.sysctl.kernel.msgmnb' => 1,
671 'lxc.sysctl.kernel.msgmni' => 1,
672 'lxc.sysctl.kernel.sem' => 1,
673 'lxc.sysctl.kernel.shmall' => 1,
674 'lxc.sysctl.kernel.shmmax' => 1,
675 'lxc.sysctl.kernel.shmmni' => 1,
676 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
679 my $deprecated_lxc_conf_keys = {
680 # Deprecated (removed with lxc 3.0):
681 'lxc.aa_profile' => 'lxc.apparmor.profile',
682 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
683 'lxc.console' => 'lxc.console.path',
684 'lxc.devttydir' => 'lxc.tty.dir',
685 'lxc.haltsignal' => 'lxc.signal.halt',
686 'lxc.rebootsignal' => 'lxc.signal.reboot',
687 'lxc.stopsignal' => 'lxc.signal.stop',
688 'lxc.id_map' => 'lxc.idmap',
689 'lxc.init_cmd' => 'lxc.init.cmd',
690 'lxc.loglevel' => 'lxc.log.level',
691 'lxc.logfile' => 'lxc.log.file',
692 'lxc.mount' => 'lxc.mount.fstab',
693 'lxc.network.type' => 'lxc.net.INDEX.type',
694 'lxc.network.flags' => 'lxc.net.INDEX.flags',
695 'lxc.network.link' => 'lxc.net.INDEX.link',
696 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
697 'lxc.network.name' => 'lxc.net.INDEX.name',
698 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
699 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
700 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
701 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
702 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
703 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
704 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
705 'lxc.pts' => 'lxc.pty.max',
706 'lxc.se_context' => 'lxc.selinux.context',
707 'lxc.seccomp' => 'lxc.seccomp.profile',
708 'lxc.tty' => 'lxc.tty.max',
709 'lxc.utsname' => 'lxc.uts.name',
712 sub is_valid_lxc_conf_key
{
713 my ($vmid, $key) = @_;
714 if ($key =~ /^lxc\.limit\./) {
715 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
718 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
719 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
722 my $validity = $valid_lxc_conf_keys->{$key};
723 return $validity if defined($validity);
724 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
725 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
726 || $key =~ /^lxc\.net\./; # allow custom network definitions
730 our $netconf_desc = {
734 description
=> "Network interface type.",
739 format_description
=> 'string',
740 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
741 pattern
=> '[-_.\w\d]+',
745 format_description
=> 'bridge',
746 description
=> 'Bridge to attach the network device to.',
747 pattern
=> '[-_.\w\d]+',
750 hwaddr
=> get_standard_option
('mac-addr', {
751 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)',
755 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
756 minimum
=> 64, # minimum ethernet frame is 64 bytes
761 format
=> 'pve-ipv4-config',
762 format_description
=> '(IPv4/CIDR|dhcp|manual)',
763 description
=> 'IPv4 address in CIDR format.',
769 format_description
=> 'GatewayIPv4',
770 description
=> 'Default gateway for IPv4 traffic.',
775 format
=> 'pve-ipv6-config',
776 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
777 description
=> 'IPv6 address in CIDR format.',
783 format_description
=> 'GatewayIPv6',
784 description
=> 'Default gateway for IPv6 traffic.',
789 description
=> "Controls whether this interface's firewall rules should be used.",
796 description
=> "VLAN tag for this interface.",
801 pattern
=> qr/\d+(?:;\d+)*/,
802 format_description
=> 'vlanid[;vlanid...]',
803 description
=> "VLAN ids to pass through the interface",
808 format_description
=> 'mbps',
809 description
=> "Apply rate limiting to the interface",
813 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
815 my $MAX_LXC_NETWORKS = 32;
816 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
817 $confdesc->{"net$i"} = {
819 type
=> 'string', format
=> $netconf_desc,
820 description
=> "Specifies network interfaces for the container.",
824 PVE
::JSONSchema
::register_format
('pve-ct-timezone', \
&verify_ct_timezone
);
825 sub verify_ct_timezone
{
826 my ($timezone, $noerr) = @_;
828 return if $timezone eq 'host'; # using host settings
830 PVE
::JSONSchema
::pve_verify_timezone
($timezone);
833 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
834 sub verify_lxc_mp_string
{
835 my ($mp, $noerr) = @_;
839 # /. or /.. at the end
840 # ../ at the beginning
842 if($mp =~ m
@/\.\
.?
/@ ||
845 return undef if $noerr;
846 die "$mp contains illegal character sequences\n";
855 description
=> 'Whether to include the mount point in backups.',
856 verbose_description
=> 'Whether to include the mount point in backups '.
857 '(only used for volume mount points).',
862 format
=> 'pve-lxc-mp-string',
863 format_description
=> 'Path',
864 description
=> 'Path to the mount point as seen from inside the container '.
865 '(must not contain symlinks).',
866 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
867 "NOTE: Must not contain any symlinks for security reasons."
870 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
876 format
=> 'pve-volume-id',
877 format_description
=> 'volume',
878 description
=> 'The volume that is not used currently.',
882 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
883 $confdesc->{"mp$i"} = {
885 type
=> 'string', format
=> $mp_desc,
886 description
=> "Use volume as container mount point. Use the special " .
887 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
892 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
893 $confdesc->{"unused$i"} = {
895 type
=> 'string', format
=> $unused_desc,
896 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
900 sub parse_pct_config
{
901 my ($filename, $raw, $strict) = @_;
903 return undef if !defined($raw);
906 digest
=> Digest
::SHA
::sha1_hex
($raw),
911 my $handle_error = sub {
921 $filename =~ m
|/lxc/(\d
+).conf
$|
922 || die "got strange filename '$filename'";
930 my @lines = split(/\n/, $raw);
931 foreach my $line (@lines) {
932 next if $line =~ m/^\s*$/;
934 if ($line =~ m/^\[pve:pending\]\s*$/i) {
935 $section = 'pending';
936 $conf->{description
} = $descr if $descr;
938 $conf = $res->{$section} = {};
940 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
942 $conf->{description
} = $descr if $descr;
944 $conf = $res->{snapshots
}->{$section} = {};
948 if ($line =~ m/^\#(.*)$/) {
949 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
953 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
956 my $validity = is_valid_lxc_conf_key
($vmid, $key);
957 if ($validity eq 1) {
958 push @{$conf->{lxc
}}, [$key, $value];
959 } elsif (my $errmsg = $validity) {
960 $handle_error->("vm $vmid - $key: $errmsg\n");
962 $handle_error->("vm $vmid - unable to parse config: $line\n");
964 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
965 $descr .= PVE
::Tools
::decode_text
($2);
966 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
967 $conf->{snapstate
} = $1;
968 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
970 if ($section eq 'pending') {
971 $conf->{delete} = $value;
973 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
975 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
978 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
979 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
980 $conf->{$key} = $value;
982 $handle_error->("vm $vmid - unable to parse config: $line\n");
986 $conf->{description
} = $descr if $descr;
988 delete $res->{snapstate
}; # just to be sure
993 sub write_pct_config
{
994 my ($filename, $conf) = @_;
996 delete $conf->{snapstate
}; # just to be sure
998 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
999 my $used_volids = {};
1000 foreach my $vid (@$volidlist) {
1001 $used_volids->{$vid} = 1;
1004 # remove 'unusedX' settings if the volume is still used
1005 foreach my $key (keys %$conf) {
1006 my $value = $conf->{$key};
1007 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1008 delete $conf->{$key};
1012 my $generate_raw_config = sub {
1017 # add description as comment to top of file
1018 my $descr = $conf->{description
} || '';
1019 foreach my $cl (split(/\n/, $descr)) {
1020 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
1023 foreach my $key (sort keys %$conf) {
1024 next if $key eq 'digest' || $key eq 'description' ||
1025 $key eq 'pending' || $key eq 'snapshots' ||
1026 $key eq 'snapname' || $key eq 'lxc';
1027 my $value = $conf->{$key};
1028 die "detected invalid newline inside property '$key'\n"
1030 $raw .= "$key: $value\n";
1033 if (my $lxcconf = $conf->{lxc
}) {
1034 foreach my $entry (@$lxcconf) {
1035 my ($k, $v) = @$entry;
1043 my $raw = &$generate_raw_config($conf);
1045 if (scalar(keys %{$conf->{pending
}})){
1046 $raw .= "\n[pve:pending]\n";
1047 $raw .= &$generate_raw_config($conf->{pending
});
1050 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
1051 $raw .= "\n[$snapname]\n";
1052 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
1058 sub update_pct_config
{
1059 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1061 my $storage_cfg = PVE
::Storage
::config
();
1063 foreach my $opt (@$revert) {
1064 delete $conf->{pending
}->{$opt};
1065 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1068 # write updates to pending section
1069 my $modified = {}; # record modified options
1071 foreach my $opt (@$delete) {
1072 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1073 warn "cannot delete '$opt' - not set in current configuration!\n";
1076 $modified->{$opt} = 1;
1077 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1078 die "unable to delete required option '$opt'\n";
1079 } elsif ($opt =~ m/^unused(\d+)$/) {
1080 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1081 } elsif ($opt =~ m/^mp(\d+)$/) {
1082 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1083 } elsif ($opt eq 'unprivileged') {
1084 die "unable to delete read-only option: '$opt'\n";
1086 $class->add_to_pending_delete($conf, $opt);
1089 my $check_content_type = sub {
1091 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
1092 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
1093 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1094 if !$storage_config->{content
}->{rootdir
};
1097 foreach my $opt (sort keys %$param) { # add/change
1098 $modified->{$opt} = 1;
1099 my $value = $param->{$opt};
1100 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1101 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1102 my $mp = $class->parse_volume($opt, $value);
1103 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
1104 } elsif ($opt eq 'hookscript') {
1105 PVE
::GuestHelpers
::check_hookscript
($value);
1106 } elsif ($opt eq 'nameserver') {
1107 $value = PVE
::LXC
::verify_nameserver_list
($value);
1108 } elsif ($opt eq 'searchdomain') {
1109 $value = PVE
::LXC
::verify_searchdomain_list
($value);
1110 } elsif ($opt eq 'unprivileged') {
1111 die "unable to modify read-only option: '$opt'\n";
1113 $conf->{pending
}->{$opt} = $value;
1114 $class->remove_from_pending_delete($conf, $opt);
1117 my $changes = $class->cleanup_pending($conf);
1121 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1123 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1130 my ($class, $key, $value) = @_;
1132 die "unknown setting '$key'\n" if !$confdesc->{$key};
1134 my $type = $confdesc->{$key}->{type
};
1136 if (!defined($value)) {
1137 die "got undefined value\n";
1140 if ($value =~ m/[\n\r]/) {
1141 die "property contains a line feed\n";
1144 if ($type eq 'boolean') {
1145 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1146 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1147 die "type check ('boolean') failed - got '$value'\n";
1148 } elsif ($type eq 'integer') {
1149 return int($1) if $value =~ m/^(\d+)$/;
1150 die "type check ('integer') failed - got '$value'\n";
1151 } elsif ($type eq 'number') {
1152 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1153 die "type check ('number') failed - got '$value'\n";
1154 } elsif ($type eq 'string') {
1155 if (my $fmt = $confdesc->{$key}->{format
}) {
1156 PVE
::JSONSchema
::check_format
($fmt, $value);
1161 die "internal error"
1166 # add JSON properties for create and set function
1167 sub json_config_properties
{
1168 my ($class, $prop) = @_;
1170 foreach my $opt (keys %$confdesc) {
1171 next if $opt eq 'parent' || $opt eq 'snaptime';
1172 next if $prop->{$opt};
1173 $prop->{$opt} = $confdesc->{$opt};
1179 my $parse_ct_mountpoint_full = sub {
1180 my ($class, $desc, $data, $noerr) = @_;
1185 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1187 return undef if $noerr;
1191 if (defined(my $size = $res->{size
})) {
1192 $size = PVE
::JSONSchema
::parse_size
($size);
1193 if (!defined($size)) {
1194 return undef if $noerr;
1195 die "invalid size: $size\n";
1197 $res->{size
} = $size;
1200 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1205 sub print_ct_mountpoint
{
1206 my ($class, $info, $nomp) = @_;
1207 my $skip = [ 'type' ];
1208 push @$skip, 'mp' if $nomp;
1209 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1212 sub print_ct_unused
{
1213 my ($class, $info) = @_;
1215 my $skip = [ 'type' ];
1216 return PVE
::JSONSchema
::print_property_string
($info, $unused_desc, $skip);
1220 my ($class, $key, $volume_string, $noerr) = @_;
1222 if ($key eq 'rootfs') {
1223 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1224 $res->{mp
} = '/' if defined($res);
1226 } elsif ($key =~ m/^mp\d+$/) {
1227 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1228 } elsif ($key =~ m/^unused\d+$/) {
1229 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1232 die "parse_volume - unknown type: $key\n" if !$noerr;
1238 my ($class, $key, $volume) = @_;
1240 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1242 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1251 sub print_lxc_network
{
1252 my ($class, $net) = @_;
1253 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1256 sub parse_lxc_network
{
1257 my ($class, $data) = @_;
1261 return $res if !$data;
1263 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1265 $res->{type
} = 'veth';
1266 if (!$res->{hwaddr
}) {
1267 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1268 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1274 sub parse_features
{
1275 my ($class, $data) = @_;
1276 return {} if !$data;
1277 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1281 my ($class, $name) = @_;
1283 return defined($confdesc->{$name});
1285 # END JSON config code
1287 my $LXC_FASTPLUG_OPTIONS= {
1299 sub vmconfig_hotplug_pending
{
1300 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1302 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1303 my $rootdir = "/proc/$pid/root";
1305 my $add_hotplug_error = sub {
1306 my ($opt, $msg) = @_;
1307 $errors->{$opt} = "unable to hotplug $opt: $msg";
1310 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1311 next if $selection && !$selection->{$opt};
1312 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1313 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1317 my $cgroup = PVE
::LXC
::CGroup-
>new($vmid);
1319 # There's no separate swap size to configure, there's memory and "total"
1320 # memory (iow. memory+swap). This means we have to change them together.
1321 my $hotplug_memory_done;
1322 my $hotplug_memory = sub {
1323 my ($wanted_memory, $wanted_swap) = @_;
1325 $wanted_memory = int($wanted_memory * 1024 * 1024) if defined($wanted_memory);
1326 $wanted_swap = int($wanted_swap * 1024 * 1024) if defined($wanted_swap);
1327 $cgroup->change_memory_limit($wanted_memory, $wanted_swap);
1329 $hotplug_memory_done = 1;
1332 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1333 # FIXME: $force deletion is not implemented for CTs
1334 foreach my $opt (sort keys %$pending_delete_hash) {
1335 next if $selection && !$selection->{$opt};
1337 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1339 } elsif ($opt =~ m/^unused(\d+)$/) {
1340 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1341 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1342 } elsif ($opt eq 'swap') {
1343 $hotplug_memory->(undef, 0);
1344 } elsif ($opt eq 'cpulimit') {
1345 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1346 } elsif ($opt eq 'cpuunits') {
1347 $cgroup->change_cpu_shares(undef, $confdesc->{cpuunits
}->{default});
1348 } elsif ($opt =~ m/^net(\d)$/) {
1350 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1352 die "skip\n"; # skip non-hotpluggable opts
1356 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1358 delete $conf->{$opt};
1359 $class->remove_from_pending_delete($conf, $opt);
1363 foreach my $opt (sort keys %{$conf->{pending
}}) {
1364 next if $opt eq 'delete'; # just to be sure
1365 next if $selection && !$selection->{$opt};
1366 my $value = $conf->{pending
}->{$opt};
1368 if ($opt eq 'cpulimit') {
1369 my $quota = 100000 * $value;
1370 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1371 } elsif ($opt eq 'cpuunits') {
1372 $cgroup->change_cpu_shares($value, $confdesc->{cpuunits
}->{default});
1373 } elsif ($opt =~ m/^net(\d+)$/) {
1375 my $net = $class->parse_lxc_network($value);
1376 $value = $class->print_lxc_network($net);
1377 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1378 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1379 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1380 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1382 } elsif ($opt =~ m/^mp(\d+)$/) {
1383 if (!PVE
::LXC
::Tools
::can_use_new_mount_api
()) {
1387 if (exists($conf->{$opt})) {
1388 die "skip\n"; # don't try to hotplug over existing mp
1391 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1392 # apply_pending_mountpoint modifies the value if it creates a new disk
1393 $value = $conf->{pending
}->{$opt};
1395 die "skip\n"; # skip non-hotpluggable
1399 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1401 $conf->{$opt} = $value;
1402 delete $conf->{pending
}->{$opt};
1407 sub vmconfig_apply_pending
{
1408 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1410 my $add_apply_error = sub {
1411 my ($opt, $msg) = @_;
1412 my $err_msg = "unable to apply pending change $opt : $msg";
1413 $errors->{$opt} = $err_msg;
1417 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1418 # FIXME: $force deletion is not implemented for CTs
1419 foreach my $opt (sort keys %$pending_delete_hash) {
1420 next if $selection && !$selection->{$opt};
1422 if ($opt =~ m/^mp(\d+)$/) {
1423 my $mp = $class->parse_volume($opt, $conf->{$opt});
1424 if ($mp->{type
} eq 'volume') {
1425 $class->add_unused_volume($conf, $mp->{volume
})
1426 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1428 } elsif ($opt =~ m/^unused(\d+)$/) {
1429 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1430 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1434 $add_apply_error->($opt, $err);
1436 delete $conf->{$opt};
1437 $class->remove_from_pending_delete($conf, $opt);
1441 $class->cleanup_pending($conf);
1443 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1444 next if $opt eq 'delete'; # just to be sure
1445 next if $selection && !$selection->{$opt};
1447 if ($opt =~ m/^mp(\d+)$/) {
1448 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1449 } elsif ($opt =~ m/^net(\d+)$/) {
1451 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1452 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1456 $add_apply_error->($opt, $err);
1458 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1463 my $rescan_volume = sub {
1464 my ($storecfg, $mp) = @_;
1466 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1468 warn "Could not rescan volume size - $@\n" if $@;
1471 sub apply_pending_mountpoint
{
1472 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1474 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1475 my $old = $conf->{$opt};
1476 if ($mp->{type
} eq 'volume' && $mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1477 my $original_value = $conf->{pending
}->{$opt};
1478 my $vollist = PVE
::LXC
::create_disks
(
1481 { $opt => $original_value },
1486 # Re-parse mount point:
1487 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1489 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1493 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1494 # The pending-changes code collects errors but keeps on looping through further
1495 # pending changes, so unroll the change in $conf as well if destroy_disks()
1497 $conf->{pending
}->{$opt} = $original_value;
1502 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1503 $rescan_volume->($storecfg, $mp) if $mp->{type
} eq 'volume';
1505 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1507 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1510 if (defined($old)) {
1511 my $mp = $class->parse_volume($opt, $old);
1512 if ($mp->{type
} eq 'volume') {
1513 $class->add_unused_volume($conf, $mp->{volume
})
1514 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1519 sub classify_mountpoint
{
1520 my ($class, $vol) = @_;
1521 if ($vol =~ m!^/!) {
1522 return 'device' if $vol =~ m!^/dev/!;
1528 my $__is_volume_in_use = sub {
1529 my ($class, $config, $volid) = @_;
1532 $class->foreach_volume($config, sub {
1533 my ($ms, $mountpoint) = @_;
1535 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1541 sub is_volume_in_use_by_snapshots
{
1542 my ($class, $config, $volid) = @_;
1544 if (my $snapshots = $config->{snapshots
}) {
1545 foreach my $snap (keys %$snapshots) {
1546 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1553 sub is_volume_in_use
{
1554 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1555 return 1 if $__is_volume_in_use->($class, $config, $volid);
1556 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1557 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1561 sub has_dev_console
{
1562 my ($class, $conf) = @_;
1564 return !(defined($conf->{console
}) && !$conf->{console
});
1568 my ($class, $conf, $keyname) = @_;
1570 if (my $lxcconf = $conf->{lxc
}) {
1571 foreach my $entry (@$lxcconf) {
1572 my ($key, undef) = @$entry;
1573 return 1 if $key eq $keyname;
1581 my ($class, $conf) = @_;
1583 return $conf->{tty
} // $confdesc->{tty
}->{default};
1587 my ($class, $conf) = @_;
1589 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1592 sub valid_volume_keys
{
1593 my ($class, $reverse) = @_;
1595 my @names = ('rootfs');
1597 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1598 push @names, "mp$i";
1601 return $reverse ?
reverse @names : @names;
1604 sub valid_volume_keys_with_unused
{
1605 my ($class, $reverse) = @_;
1606 my @names = $class->valid_volume_keys();
1607 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1608 push @names, "unused$i";
1610 return $reverse ?
reverse @names : @names;
1613 sub get_vm_volumes
{
1614 my ($class, $conf, $excludes) = @_;
1618 $class->foreach_volume($conf, sub {
1619 my ($ms, $mountpoint) = @_;
1621 return if $excludes && $ms eq $excludes;
1623 my $volid = $mountpoint->{volume
};
1624 return if !$volid || $mountpoint->{type
} ne 'volume';
1626 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1629 push @$vollist, $volid;
1635 sub get_replicatable_volumes
{
1636 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1640 my $test_volid = sub {
1641 my ($volid, $mountpoint) = @_;
1645 my $mptype = $mountpoint->{type
};
1646 my $replicate = $mountpoint->{replicate
} // 1;
1648 if ($mptype ne 'volume') {
1649 # skip bindmounts if replicate = 0 even for cleanup,
1650 # since bind mounts could not have been replicated ever
1651 return if !$replicate;
1652 die "unable to replicate mountpoint type '$mptype'\n";
1655 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1656 return if !$storeid;
1658 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1659 return if $scfg->{shared
};
1661 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1662 return if !$owner || ($owner != $vmid);
1664 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1666 return if !$cleanup && !$replicate;
1668 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1669 return if $cleanup || $noerr;
1670 die "missing replicate feature on volume '$volid'\n";
1673 $volhash->{$volid} = 1;
1676 $class->foreach_volume($conf, sub {
1677 my ($ms, $mountpoint) = @_;
1678 $test_volid->($mountpoint->{volume
}, $mountpoint);
1681 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1682 my $snap = $conf->{snapshots
}->{$snapname};
1683 $class->foreach_volume($snap, sub {
1684 my ($ms, $mountpoint) = @_;
1685 $test_volid->($mountpoint->{volume
}, $mountpoint);
1689 # add 'unusedX' volumes to volhash
1690 foreach my $key (keys %$conf) {
1691 if ($key =~ m/^unused/) {
1692 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });
1699 sub get_backup_volumes
{
1700 my ($class, $conf) = @_;
1702 my $return_volumes = [];
1704 my $test_mountpoint = sub {
1705 my ($key, $volume) = @_;
1707 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1709 push @$return_volumes, {
1711 included
=> $included,
1713 volume_config
=> $volume,
1717 PVE
::LXC
::Config-
>foreach_volume($conf, $test_mountpoint);
1719 return $return_volumes;