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,
27 require PVE
::Network
::SDN
::Vnets
;
31 my $nodename = PVE
::INotify
::nodename
();
32 my $lock_handles = {};
33 my $lockdir = "/run/lock/lxc";
35 mkdir "/etc/pve/nodes/$nodename/lxc";
36 my $MAX_MOUNT_POINTS = 256;
37 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
39 # BEGIN implemented abstract methods from PVE::AbstractConfig
45 sub __config_max_unused_disks
{
48 return $MAX_UNUSED_DISKS;
51 sub config_file_lock
{
52 my ($class, $vmid) = @_;
54 return "$lockdir/pve-config-${vmid}.lock";
58 my ($class, $vmid, $node) = @_;
60 $node = $nodename if !$node;
61 return "nodes/$node/lxc/$vmid.conf";
64 sub mountpoint_backup_enabled
{
65 my ($class, $mp_key, $mountpoint) = @_;
70 if ($mp_key eq 'rootfs') {
73 } elsif ($mountpoint->{type
} ne 'volume') {
75 $reason = 'not a volume';
76 } elsif ($mountpoint->{backup
}) {
83 return wantarray ?
($enabled, $reason) : $enabled;
87 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
91 if ($feature eq 'copy' || $feature eq 'clone') {
92 $opts = {'valid_target_formats' => ['raw', 'subvol']};
95 $class->foreach_volume($conf, sub {
96 my ($ms, $mountpoint) = @_;
98 return if $err; # skip further test
99 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
101 $err = 1 if !PVE
::Storage
::volume_has_feature
(
102 $storecfg, $feature, $mountpoint->{volume
}, $snapname, $running, $opts);
108 sub __snapshot_save_vmstate
{
109 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
110 die "implement me - snapshot_save_vmstate\n";
113 sub __snapshot_activate_storages
{
114 my ($class, $conf, $include_vmstate) = @_;
116 my $storecfg = PVE
::Storage
::config
();
117 my $opts = $include_vmstate ?
{ 'extra_keys' => ['vmstate'] } : {};
118 my $storage_hash = {};
120 $class->foreach_volume_full($conf, $opts, sub {
121 my ($vs, $mountpoint) = @_;
123 return if $mountpoint->{type
} ne 'volume';
125 my ($storeid) = PVE
::Storage
::parse_volume_id
($mountpoint->{volume
});
126 $storage_hash->{$storeid} = 1;
129 PVE
::Storage
::activate_storage_list
($storecfg, [ sort keys $storage_hash->%* ]);
132 sub __snapshot_check_running
{
133 my ($class, $vmid) = @_;
134 return PVE
::LXC
::check_running
($vmid);
137 sub __snapshot_check_freeze_needed
{
138 my ($class, $vmid, $config, $save_vmstate) = @_;
140 my $ret = $class->__snapshot_check_running($vmid);
144 # implements similar functionality to fsfreeze(8)
145 sub fsfreeze_mountpoint
{
146 my ($path, $thaw) = @_;
148 my $op = $thaw ?
'thaw' : 'freeze';
149 my $ioctl = $thaw ? FITHAW
: FIFREEZE
;
151 sysopen my $fd, $path, O_RDONLY
or die "failed to open $path: $!\n";
153 if (!ioctl($fd, $ioctl, 0)) {
157 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
160 sub __snapshot_freeze
{
161 my ($class, $vmid, $unfreeze) = @_;
163 my $conf = $class->load_config($vmid);
164 my $storagecfg = PVE
::Storage
::config
();
167 $class->foreach_volume($conf, sub {
168 my ($ms, $mountpoint) = @_;
170 return if $mountpoint->{type
} ne 'volume';
172 if (PVE
::Storage
::volume_snapshot_needs_fsfreeze
($storagecfg, $mountpoint->{volume
})) {
173 push @$freeze_mps, $mountpoint->{mp
};
177 my $freeze_mountpoints = sub {
180 return if scalar(@$freeze_mps) == 0;
182 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
184 for my $mp (@$freeze_mps) {
185 eval{ fsfreeze_mountpoint
("/proc/${pid}/root/${mp}", $thaw); };
191 eval { PVE
::LXC
::thaw
($vmid); };
193 $freeze_mountpoints->(1);
195 PVE
::LXC
::freeze
($vmid);
196 PVE
::LXC
::sync_container_namespace
($vmid);
197 $freeze_mountpoints->(0);
201 sub __snapshot_create_vol_snapshot
{
202 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
204 my $storecfg = PVE
::Storage
::config
();
206 return if $snapname eq 'vzdump' &&
207 !$class->mountpoint_backup_enabled($ms, $mountpoint);
209 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
212 sub __snapshot_delete_remove_drive
{
213 my ($class, $snap, $remove_drive) = @_;
215 if ($remove_drive eq 'vmstate') {
216 die "implement me - saving vmstate\n";
218 my $value = $snap->{$remove_drive};
219 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
220 delete $snap->{$remove_drive};
222 $class->add_unused_volume($snap, $mountpoint->{volume
})
223 if $mountpoint && ($mountpoint->{type
} eq 'volume');
227 sub __snapshot_delete_vmstate_file
{
228 my ($class, $snap, $force) = @_;
230 die "implement me - saving vmstate\n";
233 sub __snapshot_delete_vol_snapshot
{
234 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
236 return if $snapname eq 'vzdump' &&
237 !$class->mountpoint_backup_enabled($ms, $mountpoint);
239 my $storecfg = PVE
::Storage
::config
();
240 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
241 push @$unused, $mountpoint->{volume
};
244 sub __snapshot_rollback_vol_possible
{
245 my ($class, $mountpoint, $snapname, $blockers) = @_;
247 my $storecfg = PVE
::Storage
::config
();
248 PVE
::Storage
::volume_rollback_is_possible
(
250 $mountpoint->{volume
},
256 sub __snapshot_rollback_vol_rollback
{
257 my ($class, $mountpoint, $snapname) = @_;
259 my $storecfg = PVE
::Storage
::config
();
260 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
263 sub __snapshot_rollback_vm_stop
{
264 my ($class, $vmid) = @_;
266 PVE
::LXC
::vm_stop
($vmid, 1)
267 if $class->__snapshot_check_running($vmid);
270 sub __snapshot_rollback_vm_start
{
271 my ($class, $vmid, $vmstate, $data);
273 die "implement me - save vmstate\n";
276 sub __snapshot_rollback_get_unused
{
277 my ($class, $conf, $snap) = @_;
281 $class->foreach_volume($conf, sub {
282 my ($vs, $volume) = @_;
284 return if $volume->{type
} ne 'volume';
287 my $volid = $volume->{volume
};
289 $class->foreach_volume($snap, sub {
290 my ($ms, $mountpoint) = @_;
293 return if ($mountpoint->{type
} ne 'volume');
296 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
299 push @$unused, $volid if !$found;
305 # END implemented abstract methods from PVE::AbstractConfig
307 # BEGIN JSON config code
309 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
312 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
314 sub is_valid_mount_option
{
316 return $option =~ $valid_mount_option_re;
323 format
=> 'pve-lxc-mp-string',
324 format_description
=> 'volume',
325 description
=> 'Volume, device or directory to mount into the container.',
329 format
=> 'disk-size',
330 format_description
=> 'DiskSize',
331 description
=> 'Volume size (read only value).',
336 description
=> 'Explicitly enable or disable ACL support.',
342 description
=> 'Extra mount options for rootfs/mps.',
343 format_description
=> 'opt[;opt...]',
344 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
348 description
=> 'Read-only mount point',
353 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
358 description
=> 'Will include this volume to a storage replica job.',
364 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
365 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!",
371 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
372 type
=> 'string', format
=> $rootfs_desc,
373 description
=> "Use volume as container root.",
377 # IP address with optional interface suffix for link local ipv6 addresses
378 PVE
::JSONSchema
::register_format
('lxc-ip-with-ll-iface', \
&verify_ip_with_ll_iface
);
379 sub verify_ip_with_ll_iface
{
380 my ($addr, $noerr) = @_;
382 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
383 if (PVE
::JSONSchema
::pve_verify_ip
($addr, 1)
384 && PVE
::JSONSchema
::pve_verify_iface
($iface, 1))
390 return PVE
::JSONSchema
::pve_verify_ip
($addr, $noerr);
394 my $features_desc = {
398 description
=> "Allow mounting file systems of specific types."
399 ." This should be a list of file system types as used with the mount command."
400 ." Note that this can have negative effects on the container's security."
401 ." With access to a loop device, mounting a file can circumvent the mknod"
402 ." permission of the devices cgroup, mounting an NFS file system can"
403 ." block the host's I/O completely and prevent it from rebooting, etc.",
404 format_description
=> 'fstype;fstype;...',
405 pattern
=> qr/[a-zA-Z0-9_; ]+/,
411 description
=> "Allow nesting."
412 ." Best used with unprivileged containers with additional id mapping."
413 ." Note that this will expose procfs and sysfs contents of the host"
420 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
421 ." This is required to use docker inside a container."
422 ." By default unprivileged containers will see this system call as non-existent."
423 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
424 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
425 ." Essentially, you can choose between running systemd-networkd or docker.",
431 description
=> "Allow using 'fuse' file systems in a container."
432 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
438 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
439 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
440 ." This is experimental.",
446 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
447 ." This can break networking under newer (>= v245) systemd-network use."
455 description
=> "Lock/unlock the container.",
456 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
461 description
=> "Specifies whether a container will be started during system bootup.",
464 startup
=> get_standard_option
('pve-startup-order'),
468 description
=> "Enable/disable Template.",
474 enum
=> ['amd64', 'i386', 'arm64', 'armhf', 'riscv32', 'riscv64'],
475 description
=> "OS architecture type.",
481 enum
=> [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
482 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.",
487 description
=> "Attach a console device (/dev/console) to the container.",
493 description
=> "Specify the number of tty available to the container",
501 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
508 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.",
516 description
=> "CPU weight for a container, will be clamped to [1, 10000] in cgroup v2.",
517 verbose_description
=> "CPU weight for a container. Argument is used in the kernel fair "
518 ."scheduler. The larger the number is, the more CPU time this container gets. Number "
519 ."is relative to the weights of all the other running guests.",
522 default => 'cgroup v1: 1024, cgroup v2: 100',
527 description
=> "Amount of RAM for the container in MB.",
534 description
=> "Amount of SWAP for the container in MB.",
540 description
=> "Set a host name for the container.",
541 type
=> 'string', format
=> 'dns-name',
547 description
=> "Description for the Container. Shown in the web-interface CT's summary."
548 ." This is saved as comment inside the configuration file.",
549 maxLength
=> 1024 * 8,
553 type
=> 'string', format
=> 'dns-name-list',
554 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
558 type
=> 'string', format
=> 'lxc-ip-with-ll-iface-list',
559 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.",
563 type
=> 'string', format
=> 'pve-ct-timezone',
564 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",
566 rootfs
=> get_standard_option
('pve-ct-rootfs'),
569 type
=> 'string', format
=> 'pve-configid',
571 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
575 description
=> "Timestamp for snapshots.",
581 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).",
583 enum
=> ['shell', 'console', 'tty'],
589 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
595 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
601 format
=> $features_desc,
602 description
=> "Allow containers access to advanced features.",
607 format
=> 'pve-volume-id',
608 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
611 type
=> 'string', format
=> 'pve-tag-list',
612 description
=> 'Tags of the Container. This is only meta information.',
618 description
=> "Try to be more verbose. For now this only enables debug log-level on start.",
623 my $valid_lxc_conf_keys = {
624 'lxc.apparmor.profile' => 1,
625 'lxc.apparmor.allow_incomplete' => 1,
626 'lxc.apparmor.allow_nesting' => 1,
627 'lxc.apparmor.raw' => 1,
628 'lxc.selinux.context' => 1,
632 'lxc.signal.halt' => 1,
633 'lxc.signal.reboot' => 1,
634 'lxc.signal.stop' => 1,
637 'lxc.console.logfile' => 1,
638 'lxc.console.path' => 1,
640 'lxc.devtty.dir' => 1,
641 'lxc.hook.autodev' => 1,
644 'lxc.mount.fstab' => 1,
645 'lxc.mount.entry' => 1,
646 'lxc.mount.auto' => 1,
647 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
648 'lxc.rootfs.mount' => 1,
649 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
650 ', please use mount point options in the "rootfs" key',
656 'lxc.seccomp.profile' => 1,
657 'lxc.seccomp.notify.proxy' => 1,
658 'lxc.seccomp.notify.cookie' => 1,
660 'lxc.hook.pre-start' => 1,
661 'lxc.hook.pre-mount' => 1,
662 'lxc.hook.mount' => 1,
663 'lxc.hook.start' => 1,
664 'lxc.hook.stop' => 1,
665 'lxc.hook.post-stop' => 1,
666 'lxc.hook.clone' => 1,
667 'lxc.hook.destroy' => 1,
668 'lxc.hook.version' => 1,
669 'lxc.log.level' => 1,
671 'lxc.start.auto' => 1,
672 'lxc.start.delay' => 1,
673 'lxc.start.order' => 1,
675 'lxc.environment' => 1,
677 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
678 'lxc.sysctl.fs.mqueue' => 1,
679 'lxc.sysctl.kernel.msgmax' => 1,
680 'lxc.sysctl.kernel.msgmnb' => 1,
681 'lxc.sysctl.kernel.msgmni' => 1,
682 'lxc.sysctl.kernel.sem' => 1,
683 'lxc.sysctl.kernel.shmall' => 1,
684 'lxc.sysctl.kernel.shmmax' => 1,
685 'lxc.sysctl.kernel.shmmni' => 1,
686 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
689 my $deprecated_lxc_conf_keys = {
690 # Deprecated (removed with lxc 3.0):
691 'lxc.aa_profile' => 'lxc.apparmor.profile',
692 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
693 'lxc.console' => 'lxc.console.path',
694 'lxc.devttydir' => 'lxc.tty.dir',
695 'lxc.haltsignal' => 'lxc.signal.halt',
696 'lxc.rebootsignal' => 'lxc.signal.reboot',
697 'lxc.stopsignal' => 'lxc.signal.stop',
698 'lxc.id_map' => 'lxc.idmap',
699 'lxc.init_cmd' => 'lxc.init.cmd',
700 'lxc.loglevel' => 'lxc.log.level',
701 'lxc.logfile' => 'lxc.log.file',
702 'lxc.mount' => 'lxc.mount.fstab',
703 'lxc.network.type' => 'lxc.net.INDEX.type',
704 'lxc.network.flags' => 'lxc.net.INDEX.flags',
705 'lxc.network.link' => 'lxc.net.INDEX.link',
706 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
707 'lxc.network.name' => 'lxc.net.INDEX.name',
708 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
709 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
710 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
711 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
712 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
713 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
714 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
715 'lxc.pts' => 'lxc.pty.max',
716 'lxc.se_context' => 'lxc.selinux.context',
717 'lxc.seccomp' => 'lxc.seccomp.profile',
718 'lxc.tty' => 'lxc.tty.max',
719 'lxc.utsname' => 'lxc.uts.name',
722 sub is_valid_lxc_conf_key
{
723 my ($vmid, $key) = @_;
724 if ($key =~ /^lxc\.limit\./) {
725 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
728 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
729 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
732 my $validity = $valid_lxc_conf_keys->{$key};
733 return $validity if defined($validity);
734 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
735 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
736 || $key =~ /^lxc\.net\./; # allow custom network definitions
740 our $netconf_desc = {
744 description
=> "Network interface type.",
749 format_description
=> 'string',
750 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
751 pattern
=> '[-_.\w\d]+',
755 format_description
=> 'bridge',
756 description
=> 'Bridge to attach the network device to.',
757 pattern
=> '[-_.\w\d]+',
760 hwaddr
=> get_standard_option
('mac-addr', {
761 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)',
765 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
766 minimum
=> 64, # minimum ethernet frame is 64 bytes
772 format
=> 'pve-ipv4-config',
773 format_description
=> '(IPv4/CIDR|dhcp|manual)',
774 description
=> 'IPv4 address in CIDR format.',
780 format_description
=> 'GatewayIPv4',
781 description
=> 'Default gateway for IPv4 traffic.',
786 format
=> 'pve-ipv6-config',
787 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
788 description
=> 'IPv6 address in CIDR format.',
794 format_description
=> 'GatewayIPv6',
795 description
=> 'Default gateway for IPv6 traffic.',
800 description
=> "Controls whether this interface's firewall rules should be used.",
807 description
=> "VLAN tag for this interface.",
812 pattern
=> qr/\d+(?:;\d+)*/,
813 format_description
=> 'vlanid[;vlanid...]',
814 description
=> "VLAN ids to pass through the interface",
819 format_description
=> 'mbps',
820 description
=> "Apply rate limiting to the interface",
823 # TODO: Rename this option and the qemu-server one to `link-down` for PVE 8.0
826 description
=> 'Whether this interface should be disconnected (like pulling the plug).',
830 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
832 my $MAX_LXC_NETWORKS = 32;
833 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
834 $confdesc->{"net$i"} = {
836 type
=> 'string', format
=> $netconf_desc,
837 description
=> "Specifies network interfaces for the container.",
841 PVE
::JSONSchema
::register_format
('pve-ct-timezone', \
&verify_ct_timezone
);
842 sub verify_ct_timezone
{
843 my ($timezone, $noerr) = @_;
845 return if $timezone eq 'host'; # using host settings
847 PVE
::JSONSchema
::pve_verify_timezone
($timezone);
850 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
851 sub verify_lxc_mp_string
{
852 my ($mp, $noerr) = @_;
856 # /. or /.. at the end
857 # ../ at the beginning
859 if($mp =~ m
@/\.\
.?
/@ ||
862 return undef if $noerr;
863 die "$mp contains illegal character sequences\n";
872 description
=> 'Whether to include the mount point in backups.',
873 verbose_description
=> 'Whether to include the mount point in backups '.
874 '(only used for volume mount points).',
879 format
=> 'pve-lxc-mp-string',
880 format_description
=> 'Path',
881 description
=> 'Path to the mount point as seen from inside the container '.
882 '(must not contain symlinks).',
883 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
884 "NOTE: Must not contain any symlinks for security reasons."
887 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
893 format
=> 'pve-volume-id',
894 format_description
=> 'volume',
895 description
=> 'The volume that is not used currently.',
899 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
900 $confdesc->{"mp$i"} = {
902 type
=> 'string', format
=> $mp_desc,
903 description
=> "Use volume as container mount point. Use the special " .
904 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
909 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
910 $confdesc->{"unused$i"} = {
912 type
=> 'string', format
=> $unused_desc,
913 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
917 sub parse_pct_config
{
918 my ($filename, $raw, $strict) = @_;
920 return undef if !defined($raw);
923 digest
=> Digest
::SHA
::sha1_hex
($raw),
928 my $handle_error = sub {
938 $filename =~ m
|/lxc/(\d
+).conf
$|
939 || die "got strange filename '$filename'";
947 my @lines = split(/\n/, $raw);
948 foreach my $line (@lines) {
949 next if $line =~ m/^\s*$/;
951 if ($line =~ m/^\[pve:pending\]\s*$/i) {
952 $section = 'pending';
953 $conf->{description
} = $descr if $descr;
955 $conf = $res->{$section} = {};
957 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
959 $conf->{description
} = $descr if $descr;
961 $conf = $res->{snapshots
}->{$section} = {};
965 if ($line =~ m/^\#(.*)$/) {
966 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
970 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
973 my $validity = is_valid_lxc_conf_key
($vmid, $key);
974 if ($validity eq 1) {
975 push @{$conf->{lxc
}}, [$key, $value];
976 } elsif (my $errmsg = $validity) {
977 $handle_error->("vm $vmid - $key: $errmsg\n");
979 $handle_error->("vm $vmid - unable to parse config: $line\n");
981 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
982 $descr .= PVE
::Tools
::decode_text
($2);
983 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
984 $conf->{snapstate
} = $1;
985 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
987 if ($section eq 'pending') {
988 $conf->{delete} = $value;
990 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
992 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
995 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
996 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
997 $conf->{$key} = $value;
999 $handle_error->("vm $vmid - unable to parse config: $line\n");
1003 $conf->{description
} = $descr if $descr;
1005 delete $res->{snapstate
}; # just to be sure
1010 sub write_pct_config
{
1011 my ($filename, $conf) = @_;
1013 delete $conf->{snapstate
}; # just to be sure
1015 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1016 my $used_volids = {};
1017 foreach my $vid (@$volidlist) {
1018 $used_volids->{$vid} = 1;
1021 # remove 'unusedX' settings if the volume is still used
1022 foreach my $key (keys %$conf) {
1023 my $value = $conf->{$key};
1024 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1025 delete $conf->{$key};
1029 my $generate_raw_config = sub {
1034 # add description as comment to top of file
1035 my $descr = $conf->{description
} || '';
1036 foreach my $cl (split(/\n/, $descr)) {
1037 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
1040 foreach my $key (sort keys %$conf) {
1041 next if $key eq 'digest' || $key eq 'description' ||
1042 $key eq 'pending' || $key eq 'snapshots' ||
1043 $key eq 'snapname' || $key eq 'lxc';
1044 my $value = $conf->{$key};
1045 die "detected invalid newline inside property '$key'\n"
1047 $raw .= "$key: $value\n";
1050 if (my $lxcconf = $conf->{lxc
}) {
1051 foreach my $entry (@$lxcconf) {
1052 my ($k, $v) = @$entry;
1060 my $raw = &$generate_raw_config($conf);
1062 if (scalar(keys %{$conf->{pending
}})){
1063 $raw .= "\n[pve:pending]\n";
1064 $raw .= &$generate_raw_config($conf->{pending
});
1067 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
1068 $raw .= "\n[$snapname]\n";
1069 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
1075 sub update_pct_config
{
1076 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1078 my $storage_cfg = PVE
::Storage
::config
();
1080 foreach my $opt (@$revert) {
1081 delete $conf->{pending
}->{$opt};
1082 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1085 # write updates to pending section
1086 my $modified = {}; # record modified options
1088 foreach my $opt (@$delete) {
1089 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1090 warn "cannot delete '$opt' - not set in current configuration!\n";
1093 $modified->{$opt} = 1;
1094 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1095 die "unable to delete required option '$opt'\n";
1096 } elsif ($opt =~ m/^unused(\d+)$/) {
1097 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1098 } elsif ($opt =~ m/^mp(\d+)$/) {
1099 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1100 } elsif ($opt eq 'unprivileged') {
1101 die "unable to delete read-only option: '$opt'\n";
1103 $class->add_to_pending_delete($conf, $opt);
1106 my $check_content_type = sub {
1108 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
1109 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
1110 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1111 if !$storage_config->{content
}->{rootdir
};
1114 foreach my $opt (sort keys %$param) { # add/change
1115 $modified->{$opt} = 1;
1116 my $value = $param->{$opt};
1117 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1118 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1119 my $mp = $class->parse_volume($opt, $value);
1120 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
1121 } elsif ($opt eq 'hookscript') {
1122 PVE
::GuestHelpers
::check_hookscript
($value);
1123 } elsif ($opt eq 'nameserver') {
1124 $value = PVE
::LXC
::verify_nameserver_list
($value);
1125 } elsif ($opt eq 'searchdomain') {
1126 $value = PVE
::LXC
::verify_searchdomain_list
($value);
1127 } elsif ($opt eq 'unprivileged') {
1128 die "unable to modify read-only option: '$opt'\n";
1129 } elsif ($opt eq 'tags') {
1130 $value = PVE
::GuestHelpers
::get_unique_tags
($value);
1131 } elsif ($opt =~ m/^net(\d+)$/) {
1132 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $value);
1134 if (my $mtu = $res->{mtu
}) {
1135 my $bridge_mtu = PVE
::Network
::read_bridge_mtu
($res->{bridge
});
1136 die "$opt: MTU size '$mtu' is bigger than bridge MTU '$bridge_mtu'\n"
1137 if ($mtu > $bridge_mtu);
1140 $conf->{pending
}->{$opt} = $value;
1141 $class->remove_from_pending_delete($conf, $opt);
1144 my $changes = $class->cleanup_pending($conf);
1148 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1150 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1157 my ($class, $key, $value) = @_;
1159 die "unknown setting '$key'\n" if !$confdesc->{$key};
1161 my $type = $confdesc->{$key}->{type
};
1163 if (!defined($value)) {
1164 die "got undefined value\n";
1167 if ($value =~ m/[\n\r]/) {
1168 die "property contains a line feed\n";
1171 if ($type eq 'boolean') {
1172 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1173 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1174 die "type check ('boolean') failed - got '$value'\n";
1175 } elsif ($type eq 'integer') {
1176 return int($1) if $value =~ m/^(\d+)$/;
1177 die "type check ('integer') failed - got '$value'\n";
1178 } elsif ($type eq 'number') {
1179 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1180 die "type check ('number') failed - got '$value'\n";
1181 } elsif ($type eq 'string') {
1182 if (my $fmt = $confdesc->{$key}->{format
}) {
1183 PVE
::JSONSchema
::check_format
($fmt, $value);
1188 die "internal error"
1193 # add JSON properties for create and set function
1194 sub json_config_properties
{
1195 my ($class, $prop) = @_;
1197 foreach my $opt (keys %$confdesc) {
1198 next if $opt eq 'parent' || $opt eq 'snaptime';
1199 next if $prop->{$opt};
1200 $prop->{$opt} = $confdesc->{$opt};
1206 my $parse_ct_mountpoint_full = sub {
1207 my ($class, $desc, $data, $noerr) = @_;
1212 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1214 return undef if $noerr;
1218 if (defined(my $size = $res->{size
})) {
1219 $size = PVE
::JSONSchema
::parse_size
($size);
1220 if (!defined($size)) {
1221 return undef if $noerr;
1222 die "invalid size: $size\n";
1224 $res->{size
} = $size;
1227 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1232 sub print_ct_mountpoint
{
1233 my ($class, $info, $nomp) = @_;
1234 my $skip = [ 'type' ];
1235 push @$skip, 'mp' if $nomp;
1236 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1239 sub print_ct_unused
{
1240 my ($class, $info) = @_;
1242 my $skip = [ 'type' ];
1243 return PVE
::JSONSchema
::print_property_string
($info, $unused_desc, $skip);
1247 my ($class, $key, $volume_string, $noerr) = @_;
1249 if ($key eq 'rootfs') {
1250 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1251 $res->{mp
} = '/' if defined($res);
1253 } elsif ($key =~ m/^mp\d+$/) {
1254 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1255 } elsif ($key =~ m/^unused\d+$/) {
1256 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1259 die "parse_volume - unknown type: $key\n" if !$noerr;
1265 my ($class, $key, $volume) = @_;
1267 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1269 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1278 sub print_lxc_network
{
1279 my ($class, $net) = @_;
1280 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1283 sub parse_lxc_network
{
1284 my ($class, $data) = @_;
1286 return {} if !$data;
1288 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1290 $res->{type
} = 'veth';
1291 if (!$res->{hwaddr
}) {
1292 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1293 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1299 sub parse_features
{
1300 my ($class, $data) = @_;
1301 return {} if !$data;
1302 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1306 my ($class, $name) = @_;
1308 return defined($confdesc->{$name});
1310 # END JSON config code
1312 # takes a max memory value as KiB and returns an tuple with max and high values
1313 sub calculate_memory_constraints
{
1316 return if !defined($memory);
1318 # cgroup memory usage is limited by the hard 'max' limit (OOM-killer enforced) and the soft
1319 # 'high' limit (cgroup processes get throttled and put under heavy reclaim pressure).
1320 my $memory_max = int($memory * 1024 * 1024);
1321 # Set the high to 1016/1024 (~99.2%) of the 'max' hard limit clamped to 128 MiB max, to scale
1322 # it for the lower range while having a decent 2^x based rest for 2^y memory configs.
1323 my $memory_high = $memory >= 16 * 1024 ?
int(($memory - 128) * 1024 * 1024) : int($memory * 1024 * 1016);
1325 return ($memory_max, $memory_high);
1328 my $LXC_FASTPLUG_OPTIONS= {
1340 sub vmconfig_hotplug_pending
{
1341 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1343 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1344 my $rootdir = "/proc/$pid/root";
1346 my $add_hotplug_error = sub {
1347 my ($opt, $msg) = @_;
1348 $errors->{$opt} = "unable to hotplug $opt: $msg";
1351 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1352 next if $selection && !$selection->{$opt};
1353 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1354 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1358 my $cgroup = PVE
::LXC
::CGroup-
>new($vmid);
1360 # There's no separate swap size to configure, there's memory and "total"
1361 # memory (iow. memory+swap). This means we have to change them together.
1362 my $hotplug_memory_done;
1363 my $hotplug_memory = sub {
1364 my ($new_memory, $new_swap) = @_;
1366 ($new_memory, my $new_memory_high) = calculate_memory_constraints
($new_memory);
1367 $new_swap = int($new_swap * 1024 * 1024) if defined($new_swap);
1368 $cgroup->change_memory_limit($new_memory, $new_swap, $new_memory_high);
1370 $hotplug_memory_done = 1;
1373 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1374 # FIXME: $force deletion is not implemented for CTs
1375 foreach my $opt (sort keys %$pending_delete_hash) {
1376 next if $selection && !$selection->{$opt};
1378 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1380 } elsif ($opt =~ m/^unused(\d+)$/) {
1381 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1382 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1383 } elsif ($opt eq 'swap') {
1384 $hotplug_memory->(undef, 0);
1385 } elsif ($opt eq 'cpulimit') {
1386 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1387 } elsif ($opt eq 'cpuunits') {
1388 $cgroup->change_cpu_shares(undef);
1389 } elsif ($opt =~ m/^net(\d)$/) {
1391 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1393 my $net = PVE
::LXC
::Config-
>parse_lxc_network($conf->{$opt});
1394 print "delete ips from $opt\n";
1395 eval { PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($net->{bridge
}, $net->{hwaddr
}, $conf->{hostname
}) };
1399 die "skip\n"; # skip non-hotpluggable opts
1403 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1405 delete $conf->{$opt};
1406 $class->remove_from_pending_delete($conf, $opt);
1410 foreach my $opt (sort keys %{$conf->{pending
}}) {
1411 next if $opt eq 'delete'; # just to be sure
1412 next if $selection && !$selection->{$opt};
1413 my $value = $conf->{pending
}->{$opt};
1415 if ($opt eq 'cpulimit') {
1416 my $quota = 100000 * $value;
1417 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1418 } elsif ($opt eq 'cpuunits') {
1419 $cgroup->change_cpu_shares($value);
1420 } elsif ($opt =~ m/^net(\d+)$/) {
1422 my $net = $class->parse_lxc_network($value);
1423 $value = $class->print_lxc_network($net);
1424 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1425 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1426 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1427 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1429 } elsif ($opt =~ m/^mp(\d+)$/) {
1430 if (exists($conf->{$opt})) {
1431 die "skip\n"; # don't try to hotplug over existing mp
1434 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1435 # apply_pending_mountpoint modifies the value if it creates a new disk
1436 $value = $conf->{pending
}->{$opt};
1438 die "skip\n"; # skip non-hotpluggable
1442 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1444 $conf->{$opt} = $value;
1445 delete $conf->{pending
}->{$opt};
1450 sub vmconfig_apply_pending
{
1451 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1453 my $add_apply_error = sub {
1454 my ($opt, $msg) = @_;
1455 my $err_msg = "unable to apply pending change $opt : $msg";
1456 $errors->{$opt} = $err_msg;
1460 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1461 # FIXME: $force deletion is not implemented for CTs
1462 foreach my $opt (sort keys %$pending_delete_hash) {
1463 next if $selection && !$selection->{$opt};
1465 if ($opt =~ m/^mp(\d+)$/) {
1466 my $mp = $class->parse_volume($opt, $conf->{$opt});
1467 if ($mp->{type
} eq 'volume') {
1468 $class->add_unused_volume($conf, $mp->{volume
})
1469 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1471 } elsif ($opt =~ m/^unused(\d+)$/) {
1472 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1473 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1474 } elsif ($opt =~ m/^net(\d+)$/) {
1476 my $net = $class->parse_lxc_network($conf->{$opt});
1477 eval { PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($net->{bridge
}, $net->{hwaddr
}, $conf->{hostname
}) };
1483 $add_apply_error->($opt, $err);
1485 delete $conf->{$opt};
1486 $class->remove_from_pending_delete($conf, $opt);
1490 $class->cleanup_pending($conf);
1492 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1493 next if $opt eq 'delete'; # just to be sure
1494 next if $selection && !$selection->{$opt};
1496 if ($opt =~ m/^mp(\d+)$/) {
1497 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1498 } elsif ($opt =~ m/^net(\d+)$/) {
1500 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1501 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1503 if ($conf->{$opt}) {
1504 my $old_net = $class->parse_lxc_network($conf->{$opt});
1505 if ($old_net->{bridge
} ne $net->{bridge
} || $old_net->{hwaddr
} ne $net->{hwaddr
}) {
1506 PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($old_net->{bridge
}, $old_net->{hwaddr
}, $conf->{name
});
1507 PVE
::Network
::SDN
::Vnets
::add_next_free_cidr
($net->{bridge
}, $conf->{hostname
}, $net->{hwaddr
}, $vmid, undef, 1);
1510 PVE
::Network
::SDN
::Vnets
::add_next_free_cidr
($net->{bridge
}, $conf->{hostname
}, $net->{hwaddr
}, $vmid, undef, 1);
1516 $add_apply_error->($opt, $err);
1518 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1523 my $rescan_volume = sub {
1524 my ($storecfg, $mp) = @_;
1526 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1528 warn "Could not rescan volume size - $@\n" if $@;
1531 sub apply_pending_mountpoint
{
1532 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1534 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1535 my $old = $conf->{$opt};
1536 if ($mp->{type
} eq 'volume' && $mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1537 my $original_value = $conf->{pending
}->{$opt};
1538 my $vollist = PVE
::LXC
::create_disks
(
1541 { $opt => $original_value },
1546 # Re-parse mount point:
1547 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1549 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1553 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1554 # The pending-changes code collects errors but keeps on looping through further
1555 # pending changes, so unroll the change in $conf as well if destroy_disks()
1557 $conf->{pending
}->{$opt} = $original_value;
1562 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1563 $rescan_volume->($storecfg, $mp) if $mp->{type
} eq 'volume';
1565 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1567 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1570 if (defined($old)) {
1571 my $mp = $class->parse_volume($opt, $old);
1572 if ($mp->{type
} eq 'volume') {
1573 $class->add_unused_volume($conf, $mp->{volume
})
1574 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1579 sub classify_mountpoint
{
1580 my ($class, $vol) = @_;
1581 if ($vol =~ m!^/!) {
1582 return 'device' if $vol =~ m!^/dev/!;
1588 my $__is_volume_in_use = sub {
1589 my ($class, $config, $volid) = @_;
1592 $class->foreach_volume($config, sub {
1593 my ($ms, $mountpoint) = @_;
1595 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1601 sub is_volume_in_use_by_snapshots
{
1602 my ($class, $config, $volid) = @_;
1604 if (my $snapshots = $config->{snapshots
}) {
1605 foreach my $snap (keys %$snapshots) {
1606 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1613 sub is_volume_in_use
{
1614 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1615 return 1 if $__is_volume_in_use->($class, $config, $volid);
1616 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1617 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1621 sub has_dev_console
{
1622 my ($class, $conf) = @_;
1624 return !(defined($conf->{console
}) && !$conf->{console
});
1628 my ($class, $conf, $keyname) = @_;
1630 if (my $lxcconf = $conf->{lxc
}) {
1631 foreach my $entry (@$lxcconf) {
1632 my ($key, undef) = @$entry;
1633 return 1 if $key eq $keyname;
1641 my ($class, $conf) = @_;
1643 return $conf->{tty
} // $confdesc->{tty
}->{default};
1647 my ($class, $conf) = @_;
1649 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1652 sub valid_volume_keys
{
1653 my ($class, $reverse) = @_;
1655 my @names = ('rootfs');
1657 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1658 push @names, "mp$i";
1661 return $reverse ?
reverse @names : @names;
1664 sub valid_volume_keys_with_unused
{
1665 my ($class, $reverse) = @_;
1666 my @names = $class->valid_volume_keys();
1667 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1668 push @names, "unused$i";
1670 return $reverse ?
reverse @names : @names;
1673 sub get_vm_volumes
{
1674 my ($class, $conf, $excludes) = @_;
1678 $class->foreach_volume($conf, sub {
1679 my ($ms, $mountpoint) = @_;
1681 return if $excludes && $ms eq $excludes;
1683 my $volid = $mountpoint->{volume
};
1684 return if !$volid || $mountpoint->{type
} ne 'volume';
1686 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1689 push @$vollist, $volid;
1695 sub get_replicatable_volumes
{
1696 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1700 my $test_volid = sub {
1701 my ($volid, $mountpoint) = @_;
1705 my $mptype = $mountpoint->{type
};
1706 my $replicate = $mountpoint->{replicate
} // 1;
1708 if ($mptype ne 'volume') {
1709 # skip bindmounts if replicate = 0 even for cleanup,
1710 # since bind mounts could not have been replicated ever
1711 return if !$replicate;
1712 die "unable to replicate mountpoint type '$mptype'\n";
1715 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1716 return if !$storeid;
1718 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1719 return if $scfg->{shared
};
1721 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1722 return if !$owner || ($owner != $vmid);
1724 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1726 return if !$cleanup && !$replicate;
1728 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1729 return if $cleanup || $noerr;
1730 die "missing replicate feature on volume '$volid'\n";
1733 $volhash->{$volid} = 1;
1736 $class->foreach_volume($conf, sub {
1737 my ($ms, $mountpoint) = @_;
1738 $test_volid->($mountpoint->{volume
}, $mountpoint);
1741 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1742 my $snap = $conf->{snapshots
}->{$snapname};
1743 $class->foreach_volume($snap, sub {
1744 my ($ms, $mountpoint) = @_;
1745 $test_volid->($mountpoint->{volume
}, $mountpoint);
1749 # add 'unusedX' volumes to volhash
1750 foreach my $key (keys %$conf) {
1751 if ($key =~ m/^unused/) {
1752 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });
1759 sub get_backup_volumes
{
1760 my ($class, $conf) = @_;
1762 my $return_volumes = [];
1764 my $test_mountpoint = sub {
1765 my ($key, $volume) = @_;
1767 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1769 push @$return_volumes, {
1771 included
=> $included,
1773 volume_config
=> $volume,
1777 PVE
::LXC
::Config-
>foreach_volume($conf, $test_mountpoint);
1779 return $return_volumes;
1782 sub get_derived_property
{
1783 my ($class, $conf, $name) = @_;
1785 if ($name eq 'max-cpu') {
1786 return $conf->{cpulimit
} || $conf->{cores
} || 0;
1787 } elsif ($name eq 'max-memory') {
1788 return ($conf->{memory
} || 512) * 1024 * 1024;
1790 die "unknown derived property - $name\n";