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);
19 use base
qw(PVE::AbstractConfig);
22 FIFREEZE
=> 0xc0045877,
28 require PVE
::Network
::SDN
::Vnets
;
32 my $nodename = PVE
::INotify
::nodename
();
33 my $lock_handles = {};
34 my $lockdir = "/run/lock/lxc";
36 mkdir "/etc/pve/nodes/$nodename/lxc";
37 my $MAX_MOUNT_POINTS = 256;
38 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
39 my $MAX_DEVICES = 256;
41 # BEGIN implemented abstract methods from PVE::AbstractConfig
47 sub __config_max_unused_disks
{
50 return $MAX_UNUSED_DISKS;
53 sub config_file_lock
{
54 my ($class, $vmid) = @_;
56 return "$lockdir/pve-config-${vmid}.lock";
60 my ($class, $vmid, $node) = @_;
62 $node = $nodename if !$node;
63 return "nodes/$node/lxc/$vmid.conf";
66 sub mountpoint_backup_enabled
{
67 my ($class, $mp_key, $mountpoint) = @_;
72 if ($mp_key eq 'rootfs') {
75 } elsif ($mountpoint->{type
} ne 'volume') {
77 $reason = 'not a volume';
78 } elsif ($mountpoint->{backup
}) {
85 return wantarray ?
($enabled, $reason) : $enabled;
89 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
93 if ($feature eq 'copy' || $feature eq 'clone') {
94 $opts = {'valid_target_formats' => ['raw', 'subvol']};
97 $class->foreach_volume($conf, sub {
98 my ($ms, $mountpoint) = @_;
100 return if $err; # skip further test
101 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
103 $err = 1 if !PVE
::Storage
::volume_has_feature
(
104 $storecfg, $feature, $mountpoint->{volume
}, $snapname, $running, $opts);
110 sub __snapshot_save_vmstate
{
111 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
112 die "implement me - snapshot_save_vmstate\n";
115 sub __snapshot_activate_storages
{
116 my ($class, $conf, $include_vmstate) = @_;
118 my $storecfg = PVE
::Storage
::config
();
119 my $opts = $include_vmstate ?
{ 'extra_keys' => ['vmstate'] } : {};
120 my $storage_hash = {};
122 $class->foreach_volume_full($conf, $opts, sub {
123 my ($vs, $mountpoint) = @_;
125 return if $mountpoint->{type
} ne 'volume';
127 my ($storeid) = PVE
::Storage
::parse_volume_id
($mountpoint->{volume
});
128 $storage_hash->{$storeid} = 1;
131 PVE
::Storage
::activate_storage_list
($storecfg, [ sort keys $storage_hash->%* ]);
134 sub __snapshot_check_running
{
135 my ($class, $vmid) = @_;
136 return PVE
::LXC
::check_running
($vmid);
139 sub __snapshot_check_freeze_needed
{
140 my ($class, $vmid, $config, $save_vmstate) = @_;
142 my $ret = $class->__snapshot_check_running($vmid);
146 # implements similar functionality to fsfreeze(8)
147 sub fsfreeze_mountpoint
{
148 my ($path, $thaw) = @_;
150 my $op = $thaw ?
'thaw' : 'freeze';
151 my $ioctl = $thaw ? FITHAW
: FIFREEZE
;
153 sysopen my $fd, $path, O_RDONLY
or die "failed to open $path: $!\n";
155 if (!ioctl($fd, $ioctl, 0)) {
159 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
162 sub __snapshot_freeze
{
163 my ($class, $vmid, $unfreeze) = @_;
165 my $conf = $class->load_config($vmid);
166 my $storagecfg = PVE
::Storage
::config
();
169 $class->foreach_volume($conf, sub {
170 my ($ms, $mountpoint) = @_;
172 return if $mountpoint->{type
} ne 'volume';
174 if (PVE
::Storage
::volume_snapshot_needs_fsfreeze
($storagecfg, $mountpoint->{volume
})) {
175 push @$freeze_mps, $mountpoint->{mp
};
179 my $freeze_mountpoints = sub {
182 return if scalar(@$freeze_mps) == 0;
184 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
186 for my $mp (@$freeze_mps) {
187 eval{ fsfreeze_mountpoint
("/proc/${pid}/root/${mp}", $thaw); };
193 eval { PVE
::LXC
::thaw
($vmid); };
195 $freeze_mountpoints->(1);
197 PVE
::LXC
::freeze
($vmid);
198 PVE
::LXC
::sync_container_namespace
($vmid);
199 $freeze_mountpoints->(0);
203 sub __snapshot_create_vol_snapshot
{
204 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
206 my $storecfg = PVE
::Storage
::config
();
208 return if $snapname eq 'vzdump' &&
209 !$class->mountpoint_backup_enabled($ms, $mountpoint);
211 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
214 sub __snapshot_delete_remove_drive
{
215 my ($class, $snap, $remove_drive) = @_;
217 if ($remove_drive eq 'vmstate') {
218 die "implement me - saving vmstate\n";
220 my $value = $snap->{$remove_drive};
221 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
222 delete $snap->{$remove_drive};
224 $class->add_unused_volume($snap, $mountpoint->{volume
})
225 if $mountpoint && ($mountpoint->{type
} eq 'volume');
229 sub __snapshot_delete_vmstate_file
{
230 my ($class, $snap, $force) = @_;
232 die "implement me - saving vmstate\n";
235 sub __snapshot_delete_vol_snapshot
{
236 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
238 return if $snapname eq 'vzdump' &&
239 !$class->mountpoint_backup_enabled($ms, $mountpoint);
241 my $storecfg = PVE
::Storage
::config
();
242 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
243 push @$unused, $mountpoint->{volume
};
246 sub __snapshot_rollback_vol_possible
{
247 my ($class, $mountpoint, $snapname, $blockers) = @_;
249 my $storecfg = PVE
::Storage
::config
();
250 PVE
::Storage
::volume_rollback_is_possible
(
252 $mountpoint->{volume
},
258 sub __snapshot_rollback_vol_rollback
{
259 my ($class, $mountpoint, $snapname) = @_;
261 my $storecfg = PVE
::Storage
::config
();
262 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
265 sub __snapshot_rollback_vm_stop
{
266 my ($class, $vmid) = @_;
268 PVE
::LXC
::vm_stop
($vmid, 1)
269 if $class->__snapshot_check_running($vmid);
272 sub __snapshot_rollback_vm_start
{
273 my ($class, $vmid, $vmstate, $data);
275 die "implement me - save vmstate\n";
278 sub __snapshot_rollback_get_unused
{
279 my ($class, $conf, $snap) = @_;
283 $class->foreach_volume($conf, sub {
284 my ($vs, $volume) = @_;
286 return if $volume->{type
} ne 'volume';
289 my $volid = $volume->{volume
};
291 $class->foreach_volume($snap, sub {
292 my ($ms, $mountpoint) = @_;
295 return if ($mountpoint->{type
} ne 'volume');
298 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
301 push @$unused, $volid if !$found;
307 # END implemented abstract methods from PVE::AbstractConfig
309 # BEGIN JSON config code
311 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
314 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
316 sub is_valid_mount_option
{
318 return $option =~ $valid_mount_option_re;
325 format
=> 'pve-lxc-mp-string',
326 format_description
=> 'volume',
327 description
=> 'Volume, device or directory to mount into the container.',
331 format
=> 'disk-size',
332 format_description
=> 'DiskSize',
333 description
=> 'Volume size (read only value).',
338 description
=> 'Explicitly enable or disable ACL support.',
344 description
=> 'Extra mount options for rootfs/mps.',
345 format_description
=> 'opt[;opt...]',
346 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
350 description
=> 'Read-only mount point',
355 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
360 description
=> 'Will include this volume to a storage replica job.',
366 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
367 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!",
373 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
374 type
=> 'string', format
=> $rootfs_desc,
375 description
=> "Use volume as container root.",
379 # IP address with optional interface suffix for link local ipv6 addresses
380 PVE
::JSONSchema
::register_format
('lxc-ip-with-ll-iface', \
&verify_ip_with_ll_iface
);
381 sub verify_ip_with_ll_iface
{
382 my ($addr, $noerr) = @_;
384 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
385 if (PVE
::JSONSchema
::pve_verify_ip
($addr, 1)
386 && PVE
::JSONSchema
::pve_verify_iface
($iface, 1))
392 return PVE
::JSONSchema
::pve_verify_ip
($addr, $noerr);
396 my $features_desc = {
400 description
=> "Allow mounting file systems of specific types."
401 ." This should be a list of file system types as used with the mount command."
402 ." Note that this can have negative effects on the container's security."
403 ." With access to a loop device, mounting a file can circumvent the mknod"
404 ." permission of the devices cgroup, mounting an NFS file system can"
405 ." block the host's I/O completely and prevent it from rebooting, etc.",
406 format_description
=> 'fstype;fstype;...',
407 pattern
=> qr/[a-zA-Z0-9_; ]+/,
413 description
=> "Allow nesting."
414 ." Best used with unprivileged containers with additional id mapping."
415 ." Note that this will expose procfs and sysfs contents of the host"
422 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
423 ." This is required to use docker inside a container."
424 ." By default unprivileged containers will see this system call as non-existent."
425 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
426 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
427 ." Essentially, you can choose between running systemd-networkd or docker.",
433 description
=> "Allow using 'fuse' file systems in a container."
434 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
440 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
441 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
442 ." This is experimental.",
448 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
449 ." This can break networking under newer (>= v245) systemd-network use."
457 description
=> "Lock/unlock the container.",
458 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
463 description
=> "Specifies whether a container will be started during system bootup.",
466 startup
=> get_standard_option
('pve-startup-order'),
470 description
=> "Enable/disable Template.",
476 enum
=> ['amd64', 'i386', 'arm64', 'armhf', 'riscv32', 'riscv64'],
477 description
=> "OS architecture type.",
483 enum
=> [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
484 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.",
489 description
=> "Attach a console device (/dev/console) to the container.",
495 description
=> "Specify the number of tty available to the container",
503 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
510 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.",
518 description
=> "CPU weight for a container, will be clamped to [1, 10000] in cgroup v2.",
519 verbose_description
=> "CPU weight for a container. Argument is used in the kernel fair "
520 ."scheduler. The larger the number is, the more CPU time this container gets. Number "
521 ."is relative to the weights of all the other running guests.",
524 default => 'cgroup v1: 1024, cgroup v2: 100',
529 description
=> "Amount of RAM for the container in MB.",
536 description
=> "Amount of SWAP for the container in MB.",
542 description
=> "Set a host name for the container.",
543 type
=> 'string', format
=> 'dns-name',
549 description
=> "Description for the Container. Shown in the web-interface CT's summary."
550 ." This is saved as comment inside the configuration file.",
551 maxLength
=> 1024 * 8,
555 type
=> 'string', format
=> 'dns-name-list',
556 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
560 type
=> 'string', format
=> 'lxc-ip-with-ll-iface-list',
561 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.",
565 type
=> 'string', format
=> 'pve-ct-timezone',
566 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",
568 rootfs
=> get_standard_option
('pve-ct-rootfs'),
571 type
=> 'string', format
=> 'pve-configid',
573 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
577 description
=> "Timestamp for snapshots.",
583 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).",
585 enum
=> ['shell', 'console', 'tty'],
591 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
597 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
603 format
=> $features_desc,
604 description
=> "Allow containers access to advanced features.",
609 format
=> 'pve-volume-id',
610 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
613 type
=> 'string', format
=> 'pve-tag-list',
614 description
=> 'Tags of the Container. This is only meta information.',
620 description
=> "Try to be more verbose. For now this only enables debug log-level on start.",
625 my $valid_lxc_conf_keys = {
626 'lxc.apparmor.profile' => 1,
627 'lxc.apparmor.allow_incomplete' => 1,
628 'lxc.apparmor.allow_nesting' => 1,
629 'lxc.apparmor.raw' => 1,
630 'lxc.selinux.context' => 1,
634 'lxc.signal.halt' => 1,
635 'lxc.signal.reboot' => 1,
636 'lxc.signal.stop' => 1,
639 'lxc.console.logfile' => 1,
640 'lxc.console.path' => 1,
642 'lxc.devtty.dir' => 1,
643 'lxc.hook.autodev' => 1,
646 'lxc.mount.fstab' => 1,
647 'lxc.mount.entry' => 1,
648 'lxc.mount.auto' => 1,
649 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
650 'lxc.rootfs.mount' => 1,
651 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
652 ', please use mount point options in the "rootfs" key',
658 'lxc.seccomp.profile' => 1,
659 'lxc.seccomp.notify.proxy' => 1,
660 'lxc.seccomp.notify.cookie' => 1,
662 'lxc.hook.pre-start' => 1,
663 'lxc.hook.pre-mount' => 1,
664 'lxc.hook.mount' => 1,
665 'lxc.hook.start' => 1,
666 'lxc.hook.stop' => 1,
667 'lxc.hook.post-stop' => 1,
668 'lxc.hook.clone' => 1,
669 'lxc.hook.destroy' => 1,
670 'lxc.hook.version' => 1,
671 'lxc.log.level' => 1,
673 'lxc.start.auto' => 1,
674 'lxc.start.delay' => 1,
675 'lxc.start.order' => 1,
677 'lxc.environment' => 1,
679 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
680 'lxc.sysctl.fs.mqueue' => 1,
681 'lxc.sysctl.kernel.msgmax' => 1,
682 'lxc.sysctl.kernel.msgmnb' => 1,
683 'lxc.sysctl.kernel.msgmni' => 1,
684 'lxc.sysctl.kernel.sem' => 1,
685 'lxc.sysctl.kernel.shmall' => 1,
686 'lxc.sysctl.kernel.shmmax' => 1,
687 'lxc.sysctl.kernel.shmmni' => 1,
688 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
691 my $deprecated_lxc_conf_keys = {
692 # Deprecated (removed with lxc 3.0):
693 'lxc.aa_profile' => 'lxc.apparmor.profile',
694 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
695 'lxc.console' => 'lxc.console.path',
696 'lxc.devttydir' => 'lxc.tty.dir',
697 'lxc.haltsignal' => 'lxc.signal.halt',
698 'lxc.rebootsignal' => 'lxc.signal.reboot',
699 'lxc.stopsignal' => 'lxc.signal.stop',
700 'lxc.id_map' => 'lxc.idmap',
701 'lxc.init_cmd' => 'lxc.init.cmd',
702 'lxc.loglevel' => 'lxc.log.level',
703 'lxc.logfile' => 'lxc.log.file',
704 'lxc.mount' => 'lxc.mount.fstab',
705 'lxc.network.type' => 'lxc.net.INDEX.type',
706 'lxc.network.flags' => 'lxc.net.INDEX.flags',
707 'lxc.network.link' => 'lxc.net.INDEX.link',
708 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
709 'lxc.network.name' => 'lxc.net.INDEX.name',
710 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
711 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
712 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
713 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
714 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
715 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
716 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
717 'lxc.pts' => 'lxc.pty.max',
718 'lxc.se_context' => 'lxc.selinux.context',
719 'lxc.seccomp' => 'lxc.seccomp.profile',
720 'lxc.tty' => 'lxc.tty.max',
721 'lxc.utsname' => 'lxc.uts.name',
724 sub is_valid_lxc_conf_key
{
725 my ($vmid, $key) = @_;
726 if ($key =~ /^lxc\.limit\./) {
727 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
730 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
731 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
734 my $validity = $valid_lxc_conf_keys->{$key};
735 return $validity if defined($validity);
736 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
737 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
738 || $key =~ /^lxc\.net\./; # allow custom network definitions
742 our $netconf_desc = {
746 description
=> "Network interface type.",
751 format_description
=> 'string',
752 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
753 pattern
=> '[-_.\w\d]+',
757 format_description
=> 'bridge',
758 description
=> 'Bridge to attach the network device to.',
759 pattern
=> '[-_.\w\d]+',
762 hwaddr
=> get_standard_option
('mac-addr', {
763 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)',
767 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
768 minimum
=> 64, # minimum ethernet frame is 64 bytes
774 format
=> 'pve-ipv4-config',
775 format_description
=> '(IPv4/CIDR|dhcp|manual)',
776 description
=> 'IPv4 address in CIDR format.',
782 format_description
=> 'GatewayIPv4',
783 description
=> 'Default gateway for IPv4 traffic.',
788 format
=> 'pve-ipv6-config',
789 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
790 description
=> 'IPv6 address in CIDR format.',
796 format_description
=> 'GatewayIPv6',
797 description
=> 'Default gateway for IPv6 traffic.',
802 description
=> "Controls whether this interface's firewall rules should be used.",
809 description
=> "VLAN tag for this interface.",
814 pattern
=> qr/\d+(?:;\d+)*/,
815 format_description
=> 'vlanid[;vlanid...]',
816 description
=> "VLAN ids to pass through the interface",
821 format_description
=> 'mbps',
822 description
=> "Apply rate limiting to the interface",
825 # TODO: Rename this option and the qemu-server one to `link-down` for PVE 8.0
828 description
=> 'Whether this interface should be disconnected (like pulling the plug).',
832 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
834 my $MAX_LXC_NETWORKS = 32;
835 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
836 $confdesc->{"net$i"} = {
838 type
=> 'string', format
=> $netconf_desc,
839 description
=> "Specifies network interfaces for the container.",
843 PVE
::JSONSchema
::register_format
('pve-ct-timezone', \
&verify_ct_timezone
);
844 sub verify_ct_timezone
{
845 my ($timezone, $noerr) = @_;
847 return if $timezone eq 'host'; # using host settings
849 PVE
::JSONSchema
::pve_verify_timezone
($timezone);
852 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
853 sub verify_lxc_mp_string
{
854 my ($mp, $noerr) = @_;
858 # /. or /.. at the end
859 # ../ at the beginning
861 if($mp =~ m
@/\.\
.?
/@ ||
864 return undef if $noerr;
865 die "$mp contains illegal character sequences\n";
874 description
=> 'Whether to include the mount point in backups.',
875 verbose_description
=> 'Whether to include the mount point in backups '.
876 '(only used for volume mount points).',
881 format
=> 'pve-lxc-mp-string',
882 format_description
=> 'Path',
883 description
=> 'Path to the mount point as seen from inside the container '.
884 '(must not contain symlinks).',
885 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
886 "NOTE: Must not contain any symlinks for security reasons."
889 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
895 format
=> 'pve-volume-id',
896 format_description
=> 'volume',
897 description
=> 'The volume that is not used currently.',
901 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
902 $confdesc->{"mp$i"} = {
904 type
=> 'string', format
=> $mp_desc,
905 description
=> "Use volume as container mount point. Use the special " .
906 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
910 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
911 $confdesc->{"unused$i"} = {
913 type
=> 'string', format
=> $unused_desc,
914 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
918 PVE
::JSONSchema
::register_format
('pve-lxc-dev-string', \
&verify_lxc_dev_string
);
919 sub verify_lxc_dev_string
{
920 my ($dev, $noerr) = @_;
922 # do not allow /./ or /../ or /.$ or /..$
923 # enforce /dev/ at the beginning
926 $dev =~ m
@/\.\
.?
(?
:/|$)@ ||
929 return undef if $noerr;
930 die "$dev is not a valid device path\n";
941 format
=> 'pve-lxc-dev-string',
942 format_description
=> 'Path',
943 description
=> 'Device to pass through to the container',
944 verbose_description
=> 'Path to the device to pass through to the container',
949 pattern
=> '0[0-7]{3}',
950 format_description
=> 'Octal access mode',
951 description
=> 'Access mode to be set on the device node',
957 description
=> 'User ID to be assigned to the device node',
963 description
=> 'Group ID to be assigned to the device node',
967 for (my $i = 0; $i < $MAX_DEVICES; $i++) {
968 $confdesc->{"dev$i"} = {
970 type
=> 'string', format
=> $dev_desc,
971 description
=> "Device to pass through to the container",
975 sub parse_pct_config
{
976 my ($filename, $raw, $strict) = @_;
978 return undef if !defined($raw);
981 digest
=> Digest
::SHA
::sha1_hex
($raw),
986 my $handle_error = sub {
996 $filename =~ m
|/lxc/(\d
+).conf
$|
997 || die "got strange filename '$filename'";
1005 my @lines = split(/\n/, $raw);
1006 foreach my $line (@lines) {
1007 next if $line =~ m/^\s*$/;
1009 if ($line =~ m/^\[pve:pending\]\s*$/i) {
1010 $section = 'pending';
1011 $conf->{description
} = $descr if $descr;
1013 $conf = $res->{$section} = {};
1015 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
1017 $conf->{description
} = $descr if $descr;
1019 $conf = $res->{snapshots
}->{$section} = {};
1023 if ($line =~ m/^\#(.*)$/) {
1024 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
1028 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
1031 my $validity = is_valid_lxc_conf_key
($vmid, $key);
1032 if ($validity eq 1) {
1033 push @{$conf->{lxc
}}, [$key, $value];
1034 } elsif (my $errmsg = $validity) {
1035 $handle_error->("vm $vmid - $key: $errmsg\n");
1037 $handle_error->("vm $vmid - unable to parse config: $line\n");
1039 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
1040 $descr .= PVE
::Tools
::decode_text
($2);
1041 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
1042 $conf->{snapstate
} = $1;
1043 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
1045 if ($section eq 'pending') {
1046 $conf->{delete} = $value;
1048 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
1050 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
1053 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
1054 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
1055 $conf->{$key} = $value;
1057 $handle_error->("vm $vmid - unable to parse config: $line\n");
1061 $conf->{description
} = $descr if $descr;
1063 delete $res->{snapstate
}; # just to be sure
1068 sub write_pct_config
{
1069 my ($filename, $conf) = @_;
1071 delete $conf->{snapstate
}; # just to be sure
1073 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1074 my $used_volids = {};
1075 foreach my $vid (@$volidlist) {
1076 $used_volids->{$vid} = 1;
1079 # remove 'unusedX' settings if the volume is still used
1080 foreach my $key (keys %$conf) {
1081 my $value = $conf->{$key};
1082 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1083 delete $conf->{$key};
1087 my $generate_raw_config = sub {
1092 # add description as comment to top of file
1093 my $descr = $conf->{description
} || '';
1094 foreach my $cl (split(/\n/, $descr)) {
1095 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
1098 foreach my $key (sort keys %$conf) {
1099 next if $key eq 'digest' || $key eq 'description' ||
1100 $key eq 'pending' || $key eq 'snapshots' ||
1101 $key eq 'snapname' || $key eq 'lxc';
1102 my $value = $conf->{$key};
1103 die "detected invalid newline inside property '$key'\n"
1105 $raw .= "$key: $value\n";
1108 if (my $lxcconf = $conf->{lxc
}) {
1109 foreach my $entry (@$lxcconf) {
1110 my ($k, $v) = @$entry;
1118 my $raw = &$generate_raw_config($conf);
1120 if (scalar(keys %{$conf->{pending
}})){
1121 $raw .= "\n[pve:pending]\n";
1122 $raw .= &$generate_raw_config($conf->{pending
});
1125 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
1126 $raw .= "\n[$snapname]\n";
1127 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
1133 sub update_pct_config
{
1134 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1136 my $storage_cfg = PVE
::Storage
::config
();
1138 foreach my $opt (@$revert) {
1139 delete $conf->{pending
}->{$opt};
1140 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1143 # write updates to pending section
1144 my $modified = {}; # record modified options
1146 foreach my $opt (@$delete) {
1147 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1148 warn "cannot delete '$opt' - not set in current configuration!\n";
1151 $modified->{$opt} = 1;
1152 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1153 die "unable to delete required option '$opt'\n";
1154 } elsif ($opt =~ m/^unused(\d+)$/) {
1155 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1156 } elsif ($opt =~ m/^mp(\d+)$/) {
1157 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1158 } elsif ($opt eq 'unprivileged') {
1159 die "unable to delete read-only option: '$opt'\n";
1161 $class->add_to_pending_delete($conf, $opt);
1164 my $check_content_type = sub {
1166 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
1167 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
1168 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1169 if !$storage_config->{content
}->{rootdir
};
1172 foreach my $opt (sort keys %$param) { # add/change
1173 $modified->{$opt} = 1;
1174 my $value = $param->{$opt};
1175 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1176 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1177 my $mp = $class->parse_volume($opt, $value);
1178 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
1179 } elsif ($opt eq 'hookscript') {
1180 PVE
::GuestHelpers
::check_hookscript
($value);
1181 } elsif ($opt eq 'nameserver') {
1182 $value = PVE
::LXC
::verify_nameserver_list
($value);
1183 } elsif ($opt eq 'searchdomain') {
1184 $value = PVE
::LXC
::verify_searchdomain_list
($value);
1185 } elsif ($opt eq 'unprivileged') {
1186 die "unable to modify read-only option: '$opt'\n";
1187 } elsif ($opt eq 'tags') {
1188 $value = PVE
::GuestHelpers
::get_unique_tags
($value);
1189 } elsif ($opt =~ m/^net(\d+)$/) {
1190 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $value);
1192 if (my $mtu = $res->{mtu
}) {
1193 my $bridge_mtu = PVE
::Network
::read_bridge_mtu
($res->{bridge
});
1194 die "$opt: MTU size '$mtu' is bigger than bridge MTU '$bridge_mtu'\n"
1195 if ($mtu > $bridge_mtu);
1197 } elsif ($opt =~ m/^dev(\d+)$/) {
1198 my $device = $class->parse_device($value);
1200 die "Path is not defined for passthrough device $opt"
1201 if !defined($device->{path
});
1204 PVE
::LXC
::Tools
::get_device_mode_and_rdev
($device->{path
});
1206 $conf->{pending
}->{$opt} = $value;
1207 $class->remove_from_pending_delete($conf, $opt);
1210 my $changes = $class->cleanup_pending($conf);
1214 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1216 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1223 my ($class, $key, $value) = @_;
1225 die "unknown setting '$key'\n" if !$confdesc->{$key};
1227 my $type = $confdesc->{$key}->{type
};
1229 if (!defined($value)) {
1230 die "got undefined value\n";
1233 if ($value =~ m/[\n\r]/) {
1234 die "property contains a line feed\n";
1237 if ($type eq 'boolean') {
1238 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1239 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1240 die "type check ('boolean') failed - got '$value'\n";
1241 } elsif ($type eq 'integer') {
1242 return int($1) if $value =~ m/^(\d+)$/;
1243 die "type check ('integer') failed - got '$value'\n";
1244 } elsif ($type eq 'number') {
1245 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1246 die "type check ('number') failed - got '$value'\n";
1247 } elsif ($type eq 'string') {
1248 if (my $fmt = $confdesc->{$key}->{format
}) {
1249 PVE
::JSONSchema
::check_format
($fmt, $value);
1254 die "internal error"
1259 # add JSON properties for create and set function
1260 sub json_config_properties
{
1261 my ($class, $prop) = @_;
1263 foreach my $opt (keys %$confdesc) {
1264 next if $opt eq 'parent' || $opt eq 'snaptime';
1265 next if $prop->{$opt};
1266 $prop->{$opt} = $confdesc->{$opt};
1272 my $parse_ct_mountpoint_full = sub {
1273 my ($class, $desc, $data, $noerr) = @_;
1278 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1280 return undef if $noerr;
1284 if (defined(my $size = $res->{size
})) {
1285 $size = PVE
::JSONSchema
::parse_size
($size);
1286 if (!defined($size)) {
1287 return undef if $noerr;
1288 die "invalid size: $size\n";
1290 $res->{size
} = $size;
1293 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1298 sub print_ct_mountpoint
{
1299 my ($class, $info, $nomp) = @_;
1300 my $skip = [ 'type' ];
1301 push @$skip, 'mp' if $nomp;
1302 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1305 sub print_ct_unused
{
1306 my ($class, $info) = @_;
1308 my $skip = [ 'type' ];
1309 return PVE
::JSONSchema
::print_property_string
($info, $unused_desc, $skip);
1313 my ($class, $key, $volume_string, $noerr) = @_;
1315 if ($key eq 'rootfs') {
1316 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1317 $res->{mp
} = '/' if defined($res);
1319 } elsif ($key =~ m/^mp\d+$/) {
1320 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1321 } elsif ($key =~ m/^unused\d+$/) {
1322 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1325 die "parse_volume - unknown type: $key\n" if !$noerr;
1331 my ($class, $device_string, $noerr) = @_;
1333 my $res = eval { PVE
::JSONSchema
::parse_property_string
($dev_desc, $device_string) };
1335 return undef if $noerr;
1339 if (!defined($res->{path
})) {
1340 return undef if $noerr;
1341 die "Path has to be defined\n";
1348 my ($class, $key, $volume) = @_;
1350 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1352 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1361 sub print_lxc_network
{
1362 my ($class, $net) = @_;
1363 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1366 sub parse_lxc_network
{
1367 my ($class, $data) = @_;
1369 return {} if !$data;
1371 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1373 $res->{type
} = 'veth';
1374 if (!$res->{hwaddr
}) {
1375 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1376 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1382 sub parse_features
{
1383 my ($class, $data) = @_;
1384 return {} if !$data;
1385 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1389 my ($class, $name) = @_;
1391 return defined($confdesc->{$name});
1393 # END JSON config code
1395 # takes a max memory value as KiB and returns an tuple with max and high values
1396 sub calculate_memory_constraints
{
1399 return if !defined($memory);
1401 # cgroup memory usage is limited by the hard 'max' limit (OOM-killer enforced) and the soft
1402 # 'high' limit (cgroup processes get throttled and put under heavy reclaim pressure).
1403 my $memory_max = int($memory * 1024 * 1024);
1404 # Set the high to 1016/1024 (~99.2%) of the 'max' hard limit clamped to 128 MiB max, to scale
1405 # it for the lower range while having a decent 2^x based rest for 2^y memory configs.
1406 my $memory_high = $memory >= 16 * 1024 ?
int(($memory - 128) * 1024 * 1024) : int($memory * 1024 * 1016);
1408 return ($memory_max, $memory_high);
1411 my $LXC_FASTPLUG_OPTIONS= {
1423 sub vmconfig_hotplug_pending
{
1424 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1426 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1427 my $rootdir = "/proc/$pid/root";
1429 my $add_hotplug_error = sub {
1430 my ($opt, $msg) = @_;
1431 $errors->{$opt} = "unable to hotplug $opt: $msg";
1434 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1435 next if $selection && !$selection->{$opt};
1436 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1437 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1441 my $cgroup = PVE
::LXC
::CGroup-
>new($vmid);
1443 # There's no separate swap size to configure, there's memory and "total"
1444 # memory (iow. memory+swap). This means we have to change them together.
1445 my $hotplug_memory_done;
1446 my $hotplug_memory = sub {
1447 my ($new_memory, $new_swap) = @_;
1449 ($new_memory, my $new_memory_high) = calculate_memory_constraints
($new_memory);
1450 $new_swap = int($new_swap * 1024 * 1024) if defined($new_swap);
1451 $cgroup->change_memory_limit($new_memory, $new_swap, $new_memory_high);
1453 $hotplug_memory_done = 1;
1456 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1457 # FIXME: $force deletion is not implemented for CTs
1458 foreach my $opt (sort keys %$pending_delete_hash) {
1459 next if $selection && !$selection->{$opt};
1461 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1463 } elsif ($opt =~ m/^unused(\d+)$/) {
1464 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1465 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1466 } elsif ($opt eq 'swap') {
1467 $hotplug_memory->(undef, 0);
1468 } elsif ($opt eq 'cpulimit') {
1469 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1470 } elsif ($opt eq 'cpuunits') {
1471 $cgroup->change_cpu_shares(undef);
1472 } elsif ($opt =~ m/^net(\d)$/) {
1474 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1476 my $net = PVE
::LXC
::Config-
>parse_lxc_network($conf->{$opt});
1477 print "delete ips from $opt\n";
1478 eval { PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($net->{bridge
}, $net->{hwaddr
}, $conf->{hostname
}) };
1482 die "skip\n"; # skip non-hotpluggable opts
1486 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1488 delete $conf->{$opt};
1489 $class->remove_from_pending_delete($conf, $opt);
1493 foreach my $opt (sort keys %{$conf->{pending
}}) {
1494 next if $opt eq 'delete'; # just to be sure
1495 next if $selection && !$selection->{$opt};
1496 my $value = $conf->{pending
}->{$opt};
1498 if ($opt eq 'cpulimit') {
1499 my $quota = 100000 * $value;
1500 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1501 } elsif ($opt eq 'cpuunits') {
1502 $cgroup->change_cpu_shares($value);
1503 } elsif ($opt =~ m/^net(\d+)$/) {
1505 my $net = $class->parse_lxc_network($value);
1506 $value = $class->print_lxc_network($net);
1507 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1508 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1509 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1510 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1512 } elsif ($opt =~ m/^mp(\d+)$/) {
1513 if (exists($conf->{$opt})) {
1514 die "skip\n"; # don't try to hotplug over existing mp
1517 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1518 # apply_pending_mountpoint modifies the value if it creates a new disk
1519 $value = $conf->{pending
}->{$opt};
1521 die "skip\n"; # skip non-hotpluggable
1525 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1527 $conf->{$opt} = $value;
1528 delete $conf->{pending
}->{$opt};
1533 sub vmconfig_apply_pending
{
1534 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1536 my $add_apply_error = sub {
1537 my ($opt, $msg) = @_;
1538 my $err_msg = "unable to apply pending change $opt : $msg";
1539 $errors->{$opt} = $err_msg;
1543 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1544 # FIXME: $force deletion is not implemented for CTs
1545 foreach my $opt (sort keys %$pending_delete_hash) {
1546 next if $selection && !$selection->{$opt};
1548 if ($opt =~ m/^mp(\d+)$/) {
1549 my $mp = $class->parse_volume($opt, $conf->{$opt});
1550 if ($mp->{type
} eq 'volume') {
1551 $class->add_unused_volume($conf, $mp->{volume
})
1552 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1554 } elsif ($opt =~ m/^unused(\d+)$/) {
1555 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1556 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1557 } elsif ($opt =~ m/^net(\d+)$/) {
1559 my $net = $class->parse_lxc_network($conf->{$opt});
1560 eval { PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($net->{bridge
}, $net->{hwaddr
}, $conf->{hostname
}) };
1566 $add_apply_error->($opt, $err);
1568 delete $conf->{$opt};
1569 $class->remove_from_pending_delete($conf, $opt);
1573 $class->cleanup_pending($conf);
1575 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1576 next if $opt eq 'delete'; # just to be sure
1577 next if $selection && !$selection->{$opt};
1579 if ($opt =~ m/^mp(\d+)$/) {
1580 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1581 } elsif ($opt =~ m/^net(\d+)$/) {
1583 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1584 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1586 if ($conf->{$opt}) {
1587 my $old_net = $class->parse_lxc_network($conf->{$opt});
1588 if ($old_net->{bridge
} ne $net->{bridge
} || $old_net->{hwaddr
} ne $net->{hwaddr
}) {
1589 PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($old_net->{bridge
}, $old_net->{hwaddr
}, $conf->{name
});
1590 PVE
::Network
::SDN
::Vnets
::add_next_free_cidr
($net->{bridge
}, $conf->{hostname
}, $net->{hwaddr
}, $vmid, undef, 1);
1593 PVE
::Network
::SDN
::Vnets
::add_next_free_cidr
($net->{bridge
}, $conf->{hostname
}, $net->{hwaddr
}, $vmid, undef, 1);
1599 $add_apply_error->($opt, $err);
1601 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1606 my $rescan_volume = sub {
1607 my ($storecfg, $mp) = @_;
1609 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1611 warn "Could not rescan volume size - $@\n" if $@;
1614 sub apply_pending_mountpoint
{
1615 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1617 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1618 my $old = $conf->{$opt};
1619 if ($mp->{type
} eq 'volume' && $mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1620 my $original_value = $conf->{pending
}->{$opt};
1621 my $vollist = PVE
::LXC
::create_disks
(
1624 { $opt => $original_value },
1629 # Re-parse mount point:
1630 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1632 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1636 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1637 # The pending-changes code collects errors but keeps on looping through further
1638 # pending changes, so unroll the change in $conf as well if destroy_disks()
1640 $conf->{pending
}->{$opt} = $original_value;
1645 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1646 $rescan_volume->($storecfg, $mp) if $mp->{type
} eq 'volume';
1648 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1650 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1653 if (defined($old)) {
1654 my $mp = $class->parse_volume($opt, $old);
1655 if ($mp->{type
} eq 'volume') {
1656 $class->add_unused_volume($conf, $mp->{volume
})
1657 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1662 sub classify_mountpoint
{
1663 my ($class, $vol) = @_;
1664 if ($vol =~ m!^/!) {
1665 return 'device' if $vol =~ m!^/dev/!;
1671 my $__is_volume_in_use = sub {
1672 my ($class, $config, $volid) = @_;
1675 $class->foreach_volume($config, sub {
1676 my ($ms, $mountpoint) = @_;
1678 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1684 sub is_volume_in_use_by_snapshots
{
1685 my ($class, $config, $volid) = @_;
1687 if (my $snapshots = $config->{snapshots
}) {
1688 foreach my $snap (keys %$snapshots) {
1689 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1696 sub is_volume_in_use
{
1697 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1698 return 1 if $__is_volume_in_use->($class, $config, $volid);
1699 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1700 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1704 sub has_dev_console
{
1705 my ($class, $conf) = @_;
1707 return !(defined($conf->{console
}) && !$conf->{console
});
1711 my ($class, $conf, $keyname) = @_;
1713 if (my $lxcconf = $conf->{lxc
}) {
1714 foreach my $entry (@$lxcconf) {
1715 my ($key, undef) = @$entry;
1716 return 1 if $key eq $keyname;
1724 my ($class, $conf) = @_;
1726 return $conf->{tty
} // $confdesc->{tty
}->{default};
1730 my ($class, $conf) = @_;
1732 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1735 sub valid_volume_keys
{
1736 my ($class, $reverse) = @_;
1738 my @names = ('rootfs');
1740 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1741 push @names, "mp$i";
1744 return $reverse ?
reverse @names : @names;
1747 sub valid_volume_keys_with_unused
{
1748 my ($class, $reverse) = @_;
1749 my @names = $class->valid_volume_keys();
1750 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1751 push @names, "unused$i";
1753 return $reverse ?
reverse @names : @names;
1756 sub get_vm_volumes
{
1757 my ($class, $conf, $excludes) = @_;
1761 $class->foreach_volume($conf, sub {
1762 my ($ms, $mountpoint) = @_;
1764 return if $excludes && $ms eq $excludes;
1766 my $volid = $mountpoint->{volume
};
1767 return if !$volid || $mountpoint->{type
} ne 'volume';
1769 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1772 push @$vollist, $volid;
1778 sub get_replicatable_volumes
{
1779 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1783 my $test_volid = sub {
1784 my ($volid, $mountpoint) = @_;
1788 my $mptype = $mountpoint->{type
};
1789 my $replicate = $mountpoint->{replicate
} // 1;
1791 if ($mptype ne 'volume') {
1792 # skip bindmounts if replicate = 0 even for cleanup,
1793 # since bind mounts could not have been replicated ever
1794 return if !$replicate;
1795 die "unable to replicate mountpoint type '$mptype'\n";
1798 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1799 return if !$storeid;
1801 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1802 return if $scfg->{shared
};
1804 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1805 return if !$owner || ($owner != $vmid);
1807 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1809 return if !$cleanup && !$replicate;
1811 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1812 return if $cleanup || $noerr;
1813 die "missing replicate feature on volume '$volid'\n";
1816 $volhash->{$volid} = 1;
1819 $class->foreach_volume($conf, sub {
1820 my ($ms, $mountpoint) = @_;
1821 $test_volid->($mountpoint->{volume
}, $mountpoint);
1824 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1825 my $snap = $conf->{snapshots
}->{$snapname};
1826 $class->foreach_volume($snap, sub {
1827 my ($ms, $mountpoint) = @_;
1828 $test_volid->($mountpoint->{volume
}, $mountpoint);
1832 # add 'unusedX' volumes to volhash
1833 foreach my $key (keys %$conf) {
1834 if ($key =~ m/^unused/) {
1835 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });
1842 sub get_backup_volumes
{
1843 my ($class, $conf) = @_;
1845 my $return_volumes = [];
1847 my $test_mountpoint = sub {
1848 my ($key, $volume) = @_;
1850 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1852 push @$return_volumes, {
1854 included
=> $included,
1856 volume_config
=> $volume,
1860 PVE
::LXC
::Config-
>foreach_volume($conf, $test_mountpoint);
1862 return $return_volumes;
1865 sub get_derived_property
{
1866 my ($class, $conf, $name) = @_;
1868 if ($name eq 'max-cpu') {
1869 return $conf->{cpulimit
} || $conf->{cores
} || 0;
1870 } elsif ($name eq 'max-memory') {
1871 return ($conf->{memory
} || 512) * 1024 * 1024;
1873 die "unknown derived property - $name\n";
1877 sub foreach_passthrough_device
{
1878 my ($class, $conf, $func, @param) = @_;
1880 for my $key (keys %$conf) {
1881 next if $key !~ m/^dev(\d+)$/;
1883 my $device = $class->parse_device($conf->{$key});
1885 $func->($key, $device, @param);