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;
38 my $MAX_DEVICES = 256;
40 # BEGIN implemented abstract methods from PVE::AbstractConfig
46 sub __config_max_unused_disks
{
49 return $MAX_UNUSED_DISKS;
52 sub config_file_lock
{
53 my ($class, $vmid) = @_;
55 return "$lockdir/pve-config-${vmid}.lock";
59 my ($class, $vmid, $node) = @_;
61 $node = $nodename if !$node;
62 return "nodes/$node/lxc/$vmid.conf";
65 sub mountpoint_backup_enabled
{
66 my ($class, $mp_key, $mountpoint) = @_;
71 if ($mp_key eq 'rootfs') {
74 } elsif ($mountpoint->{type
} ne 'volume') {
76 $reason = 'not a volume';
77 } elsif ($mountpoint->{backup
}) {
84 return wantarray ?
($enabled, $reason) : $enabled;
88 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
92 if ($feature eq 'copy' || $feature eq 'clone') {
93 $opts = {'valid_target_formats' => ['raw', 'subvol']};
96 $class->foreach_volume($conf, sub {
97 my ($ms, $mountpoint) = @_;
99 return if $err; # skip further test
100 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
102 $err = 1 if !PVE
::Storage
::volume_has_feature
(
103 $storecfg, $feature, $mountpoint->{volume
}, $snapname, $running, $opts);
109 sub __snapshot_save_vmstate
{
110 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
111 die "implement me - snapshot_save_vmstate\n";
114 sub __snapshot_activate_storages
{
115 my ($class, $conf, $include_vmstate) = @_;
117 my $storecfg = PVE
::Storage
::config
();
118 my $opts = $include_vmstate ?
{ 'extra_keys' => ['vmstate'] } : {};
119 my $storage_hash = {};
121 $class->foreach_volume_full($conf, $opts, sub {
122 my ($vs, $mountpoint) = @_;
124 return if $mountpoint->{type
} ne 'volume';
126 my ($storeid) = PVE
::Storage
::parse_volume_id
($mountpoint->{volume
});
127 $storage_hash->{$storeid} = 1;
130 PVE
::Storage
::activate_storage_list
($storecfg, [ sort keys $storage_hash->%* ]);
133 sub __snapshot_check_running
{
134 my ($class, $vmid) = @_;
135 return PVE
::LXC
::check_running
($vmid);
138 sub __snapshot_check_freeze_needed
{
139 my ($class, $vmid, $config, $save_vmstate) = @_;
141 my $ret = $class->__snapshot_check_running($vmid);
145 # implements similar functionality to fsfreeze(8)
146 sub fsfreeze_mountpoint
{
147 my ($path, $thaw) = @_;
149 my $op = $thaw ?
'thaw' : 'freeze';
150 my $ioctl = $thaw ? FITHAW
: FIFREEZE
;
152 sysopen my $fd, $path, O_RDONLY
or die "failed to open $path: $!\n";
154 if (!ioctl($fd, $ioctl, 0)) {
158 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
161 sub __snapshot_freeze
{
162 my ($class, $vmid, $unfreeze) = @_;
164 my $conf = $class->load_config($vmid);
165 my $storagecfg = PVE
::Storage
::config
();
168 $class->foreach_volume($conf, sub {
169 my ($ms, $mountpoint) = @_;
171 return if $mountpoint->{type
} ne 'volume';
173 if (PVE
::Storage
::volume_snapshot_needs_fsfreeze
($storagecfg, $mountpoint->{volume
})) {
174 push @$freeze_mps, $mountpoint->{mp
};
178 my $freeze_mountpoints = sub {
181 return if scalar(@$freeze_mps) == 0;
183 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
185 for my $mp (@$freeze_mps) {
186 eval{ fsfreeze_mountpoint
("/proc/${pid}/root/${mp}", $thaw); };
192 eval { PVE
::LXC
::thaw
($vmid); };
194 $freeze_mountpoints->(1);
196 PVE
::LXC
::freeze
($vmid);
197 PVE
::LXC
::sync_container_namespace
($vmid);
198 $freeze_mountpoints->(0);
202 sub __snapshot_create_vol_snapshot
{
203 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
205 my $storecfg = PVE
::Storage
::config
();
207 return if $snapname eq 'vzdump' &&
208 !$class->mountpoint_backup_enabled($ms, $mountpoint);
210 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
213 sub __snapshot_delete_remove_drive
{
214 my ($class, $snap, $remove_drive) = @_;
216 if ($remove_drive eq 'vmstate') {
217 die "implement me - saving vmstate\n";
219 my $value = $snap->{$remove_drive};
220 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
221 delete $snap->{$remove_drive};
223 $class->add_unused_volume($snap, $mountpoint->{volume
})
224 if $mountpoint && ($mountpoint->{type
} eq 'volume');
228 sub __snapshot_delete_vmstate_file
{
229 my ($class, $snap, $force) = @_;
231 die "implement me - saving vmstate\n";
234 sub __snapshot_delete_vol_snapshot
{
235 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
237 return if $snapname eq 'vzdump' &&
238 !$class->mountpoint_backup_enabled($ms, $mountpoint);
240 my $storecfg = PVE
::Storage
::config
();
241 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
242 push @$unused, $mountpoint->{volume
};
245 sub __snapshot_rollback_vol_possible
{
246 my ($class, $mountpoint, $snapname, $blockers) = @_;
248 my $storecfg = PVE
::Storage
::config
();
249 PVE
::Storage
::volume_rollback_is_possible
(
251 $mountpoint->{volume
},
257 sub __snapshot_rollback_vol_rollback
{
258 my ($class, $mountpoint, $snapname) = @_;
260 my $storecfg = PVE
::Storage
::config
();
261 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
264 sub __snapshot_rollback_vm_stop
{
265 my ($class, $vmid) = @_;
267 PVE
::LXC
::vm_stop
($vmid, 1)
268 if $class->__snapshot_check_running($vmid);
271 sub __snapshot_rollback_vm_start
{
272 my ($class, $vmid, $vmstate, $data);
274 die "implement me - save vmstate\n";
277 sub __snapshot_rollback_get_unused
{
278 my ($class, $conf, $snap) = @_;
282 $class->foreach_volume($conf, sub {
283 my ($vs, $volume) = @_;
285 return if $volume->{type
} ne 'volume';
288 my $volid = $volume->{volume
};
290 $class->foreach_volume($snap, sub {
291 my ($ms, $mountpoint) = @_;
294 return if ($mountpoint->{type
} ne 'volume');
297 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
300 push @$unused, $volid if !$found;
306 # END implemented abstract methods from PVE::AbstractConfig
308 # BEGIN JSON config code
310 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
313 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
315 sub is_valid_mount_option
{
317 return $option =~ $valid_mount_option_re;
324 format
=> 'pve-lxc-mp-string',
325 format_description
=> 'volume',
326 description
=> 'Volume, device or directory to mount into the container.',
330 format
=> 'disk-size',
331 format_description
=> 'DiskSize',
332 description
=> 'Volume size (read only value).',
337 description
=> 'Explicitly enable or disable ACL support.',
343 description
=> 'Extra mount options for rootfs/mps.',
344 format_description
=> 'opt[;opt...]',
345 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
349 description
=> 'Read-only mount point',
354 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
359 description
=> 'Will include this volume to a storage replica job.',
365 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
366 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!",
372 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
373 type
=> 'string', format
=> $rootfs_desc,
374 description
=> "Use volume as container root.",
378 # IP address with optional interface suffix for link local ipv6 addresses
379 PVE
::JSONSchema
::register_format
('lxc-ip-with-ll-iface', \
&verify_ip_with_ll_iface
);
380 sub verify_ip_with_ll_iface
{
381 my ($addr, $noerr) = @_;
383 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
384 if (PVE
::JSONSchema
::pve_verify_ip
($addr, 1)
385 && PVE
::JSONSchema
::pve_verify_iface
($iface, 1))
391 return PVE
::JSONSchema
::pve_verify_ip
($addr, $noerr);
395 my $features_desc = {
399 description
=> "Allow mounting file systems of specific types."
400 ." This should be a list of file system types as used with the mount command."
401 ." Note that this can have negative effects on the container's security."
402 ." With access to a loop device, mounting a file can circumvent the mknod"
403 ." permission of the devices cgroup, mounting an NFS file system can"
404 ." block the host's I/O completely and prevent it from rebooting, etc.",
405 format_description
=> 'fstype;fstype;...',
406 pattern
=> qr/[a-zA-Z0-9_; ]+/,
412 description
=> "Allow nesting."
413 ." Best used with unprivileged containers with additional id mapping."
414 ." Note that this will expose procfs and sysfs contents of the host"
421 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
422 ." This is required to use docker inside a container."
423 ." By default unprivileged containers will see this system call as non-existent."
424 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
425 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
426 ." Essentially, you can choose between running systemd-networkd or docker.",
432 description
=> "Allow using 'fuse' file systems in a container."
433 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
439 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
440 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
441 ." This is experimental.",
447 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
448 ." This can break networking under newer (>= v245) systemd-network use."
456 description
=> "Lock/unlock the container.",
457 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
462 description
=> "Specifies whether a container will be started during system bootup.",
465 startup
=> get_standard_option
('pve-startup-order'),
469 description
=> "Enable/disable Template.",
475 enum
=> ['amd64', 'i386', 'arm64', 'armhf', 'riscv32', 'riscv64'],
476 description
=> "OS architecture type.",
482 enum
=> [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
483 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.",
488 description
=> "Attach a console device (/dev/console) to the container.",
494 description
=> "Specify the number of tty available to the container",
502 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
509 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.",
517 description
=> "CPU weight for a container, will be clamped to [1, 10000] in cgroup v2.",
518 verbose_description
=> "CPU weight for a container. Argument is used in the kernel fair "
519 ."scheduler. The larger the number is, the more CPU time this container gets. Number "
520 ."is relative to the weights of all the other running guests.",
523 default => 'cgroup v1: 1024, cgroup v2: 100',
528 description
=> "Amount of RAM for the container in MB.",
535 description
=> "Amount of SWAP for the container in MB.",
541 description
=> "Set a host name for the container.",
542 type
=> 'string', format
=> 'dns-name',
548 description
=> "Description for the Container. Shown in the web-interface CT's summary."
549 ." This is saved as comment inside the configuration file.",
550 maxLength
=> 1024 * 8,
554 type
=> 'string', format
=> 'dns-name-list',
555 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
559 type
=> 'string', format
=> 'lxc-ip-with-ll-iface-list',
560 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.",
564 type
=> 'string', format
=> 'pve-ct-timezone',
565 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",
567 rootfs
=> get_standard_option
('pve-ct-rootfs'),
570 type
=> 'string', format
=> 'pve-configid',
572 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
576 description
=> "Timestamp for snapshots.",
582 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).",
584 enum
=> ['shell', 'console', 'tty'],
590 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
596 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
602 format
=> $features_desc,
603 description
=> "Allow containers access to advanced features.",
608 format
=> 'pve-volume-id',
609 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
612 type
=> 'string', format
=> 'pve-tag-list',
613 description
=> 'Tags of the Container. This is only meta information.',
619 description
=> "Try to be more verbose. For now this only enables debug log-level on start.",
624 my $valid_lxc_conf_keys = {
625 'lxc.apparmor.profile' => 1,
626 'lxc.apparmor.allow_incomplete' => 1,
627 'lxc.apparmor.allow_nesting' => 1,
628 'lxc.apparmor.raw' => 1,
629 'lxc.selinux.context' => 1,
633 'lxc.signal.halt' => 1,
634 'lxc.signal.reboot' => 1,
635 'lxc.signal.stop' => 1,
638 'lxc.console.logfile' => 1,
639 'lxc.console.path' => 1,
641 'lxc.devtty.dir' => 1,
642 'lxc.hook.autodev' => 1,
645 'lxc.mount.fstab' => 1,
646 'lxc.mount.entry' => 1,
647 'lxc.mount.auto' => 1,
648 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
649 'lxc.rootfs.mount' => 1,
650 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
651 ', please use mount point options in the "rootfs" key',
657 'lxc.seccomp.profile' => 1,
658 'lxc.seccomp.notify.proxy' => 1,
659 'lxc.seccomp.notify.cookie' => 1,
661 'lxc.hook.pre-start' => 1,
662 'lxc.hook.pre-mount' => 1,
663 'lxc.hook.mount' => 1,
664 'lxc.hook.start' => 1,
665 'lxc.hook.stop' => 1,
666 'lxc.hook.post-stop' => 1,
667 'lxc.hook.clone' => 1,
668 'lxc.hook.destroy' => 1,
669 'lxc.hook.version' => 1,
670 'lxc.log.level' => 1,
672 'lxc.start.auto' => 1,
673 'lxc.start.delay' => 1,
674 'lxc.start.order' => 1,
676 'lxc.environment' => 1,
678 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
679 'lxc.sysctl.fs.mqueue' => 1,
680 'lxc.sysctl.kernel.msgmax' => 1,
681 'lxc.sysctl.kernel.msgmnb' => 1,
682 'lxc.sysctl.kernel.msgmni' => 1,
683 'lxc.sysctl.kernel.sem' => 1,
684 'lxc.sysctl.kernel.shmall' => 1,
685 'lxc.sysctl.kernel.shmmax' => 1,
686 'lxc.sysctl.kernel.shmmni' => 1,
687 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
690 my $deprecated_lxc_conf_keys = {
691 # Deprecated (removed with lxc 3.0):
692 'lxc.aa_profile' => 'lxc.apparmor.profile',
693 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
694 'lxc.console' => 'lxc.console.path',
695 'lxc.devttydir' => 'lxc.tty.dir',
696 'lxc.haltsignal' => 'lxc.signal.halt',
697 'lxc.rebootsignal' => 'lxc.signal.reboot',
698 'lxc.stopsignal' => 'lxc.signal.stop',
699 'lxc.id_map' => 'lxc.idmap',
700 'lxc.init_cmd' => 'lxc.init.cmd',
701 'lxc.loglevel' => 'lxc.log.level',
702 'lxc.logfile' => 'lxc.log.file',
703 'lxc.mount' => 'lxc.mount.fstab',
704 'lxc.network.type' => 'lxc.net.INDEX.type',
705 'lxc.network.flags' => 'lxc.net.INDEX.flags',
706 'lxc.network.link' => 'lxc.net.INDEX.link',
707 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
708 'lxc.network.name' => 'lxc.net.INDEX.name',
709 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
710 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
711 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
712 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
713 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
714 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
715 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
716 'lxc.pts' => 'lxc.pty.max',
717 'lxc.se_context' => 'lxc.selinux.context',
718 'lxc.seccomp' => 'lxc.seccomp.profile',
719 'lxc.tty' => 'lxc.tty.max',
720 'lxc.utsname' => 'lxc.uts.name',
723 sub is_valid_lxc_conf_key
{
724 my ($vmid, $key) = @_;
725 if ($key =~ /^lxc\.limit\./) {
726 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
729 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
730 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
733 my $validity = $valid_lxc_conf_keys->{$key};
734 return $validity if defined($validity);
735 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
736 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
737 || $key =~ /^lxc\.net\./; # allow custom network definitions
741 our $netconf_desc = {
745 description
=> "Network interface type.",
750 format_description
=> 'string',
751 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
752 pattern
=> '[-_.\w\d]+',
756 format_description
=> 'bridge',
757 description
=> 'Bridge to attach the network device to.',
758 pattern
=> '[-_.\w\d]+',
761 hwaddr
=> get_standard_option
('mac-addr', {
762 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)',
766 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
767 minimum
=> 64, # minimum ethernet frame is 64 bytes
773 format
=> 'pve-ipv4-config',
774 format_description
=> '(IPv4/CIDR|dhcp|manual)',
775 description
=> 'IPv4 address in CIDR format.',
781 format_description
=> 'GatewayIPv4',
782 description
=> 'Default gateway for IPv4 traffic.',
787 format
=> 'pve-ipv6-config',
788 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
789 description
=> 'IPv6 address in CIDR format.',
795 format_description
=> 'GatewayIPv6',
796 description
=> 'Default gateway for IPv6 traffic.',
801 description
=> "Controls whether this interface's firewall rules should be used.",
808 description
=> "VLAN tag for this interface.",
813 pattern
=> qr/\d+(?:;\d+)*/,
814 format_description
=> 'vlanid[;vlanid...]',
815 description
=> "VLAN ids to pass through the interface",
820 format_description
=> 'mbps',
821 description
=> "Apply rate limiting to the interface",
824 # TODO: Rename this option and the qemu-server one to `link-down` for PVE 8.0
827 description
=> 'Whether this interface should be disconnected (like pulling the plug).',
831 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
833 my $MAX_LXC_NETWORKS = 32;
834 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
835 $confdesc->{"net$i"} = {
837 type
=> 'string', format
=> $netconf_desc,
838 description
=> "Specifies network interfaces for the container.",
842 PVE
::JSONSchema
::register_format
('pve-ct-timezone', \
&verify_ct_timezone
);
843 sub verify_ct_timezone
{
844 my ($timezone, $noerr) = @_;
846 return if $timezone eq 'host'; # using host settings
848 PVE
::JSONSchema
::pve_verify_timezone
($timezone);
851 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
852 sub verify_lxc_mp_string
{
853 my ($mp, $noerr) = @_;
857 # /. or /.. at the end
858 # ../ at the beginning
860 if($mp =~ m
@/\.\
.?
/@ ||
863 return undef if $noerr;
864 die "$mp contains illegal character sequences\n";
873 description
=> 'Whether to include the mount point in backups.',
874 verbose_description
=> 'Whether to include the mount point in backups '.
875 '(only used for volume mount points).',
880 format
=> 'pve-lxc-mp-string',
881 format_description
=> 'Path',
882 description
=> 'Path to the mount point as seen from inside the container '.
883 '(must not contain symlinks).',
884 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
885 "NOTE: Must not contain any symlinks for security reasons."
888 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
894 format
=> 'pve-volume-id',
895 format_description
=> 'volume',
896 description
=> 'The volume that is not used currently.',
900 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
901 $confdesc->{"mp$i"} = {
903 type
=> 'string', format
=> $mp_desc,
904 description
=> "Use volume as container mount point. Use the special " .
905 "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 PVE
::JSONSchema
::register_format
('pve-lxc-dev-string', \
&verify_lxc_dev_string
);
918 sub verify_lxc_dev_string
{
919 my ($dev, $noerr) = @_;
921 # do not allow /./ or /../ or /.$ or /..$
922 # enforce /dev/ at the beginning
925 $dev =~ m
@/\.\
.?
(?
:/|$)@ ||
928 return undef if $noerr;
929 die "$dev is not a valid device path\n";
940 format
=> 'pve-lxc-dev-string',
941 format_description
=> 'Path',
942 description
=> 'Device to pass through to the container',
943 verbose_description
=> 'Path to the device to pass through to the container',
948 pattern
=> '0[0-7]{3}',
949 format_description
=> 'Octal access mode',
950 description
=> 'Access mode to be set on the device node',
956 description
=> 'User ID to be assigned to the device node',
962 description
=> 'Group ID to be assigned to the device node',
966 for (my $i = 0; $i < $MAX_DEVICES; $i++) {
967 $confdesc->{"dev$i"} = {
969 type
=> 'string', format
=> $dev_desc,
970 description
=> "Device to pass through to the container",
974 sub parse_pct_config
{
975 my ($filename, $raw, $strict) = @_;
977 return undef if !defined($raw);
980 digest
=> Digest
::SHA
::sha1_hex
($raw),
985 my $handle_error = sub {
995 $filename =~ m
|/lxc/(\d
+).conf
$|
996 || die "got strange filename '$filename'";
1004 my @lines = split(/\n/, $raw);
1005 foreach my $line (@lines) {
1006 next if $line =~ m/^\s*$/;
1008 if ($line =~ m/^\[pve:pending\]\s*$/i) {
1009 $section = 'pending';
1010 $conf->{description
} = $descr if $descr;
1012 $conf = $res->{$section} = {};
1014 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
1016 $conf->{description
} = $descr if $descr;
1018 $conf = $res->{snapshots
}->{$section} = {};
1022 if ($line =~ m/^\#(.*)$/) {
1023 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
1027 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
1030 my $validity = is_valid_lxc_conf_key
($vmid, $key);
1031 if ($validity eq 1) {
1032 push @{$conf->{lxc
}}, [$key, $value];
1033 } elsif (my $errmsg = $validity) {
1034 $handle_error->("vm $vmid - $key: $errmsg\n");
1036 $handle_error->("vm $vmid - unable to parse config: $line\n");
1038 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
1039 $descr .= PVE
::Tools
::decode_text
($2);
1040 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
1041 $conf->{snapstate
} = $1;
1042 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
1044 if ($section eq 'pending') {
1045 $conf->{delete} = $value;
1047 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
1049 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
1052 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
1053 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
1054 $conf->{$key} = $value;
1056 $handle_error->("vm $vmid - unable to parse config: $line\n");
1060 $conf->{description
} = $descr if $descr;
1062 delete $res->{snapstate
}; # just to be sure
1067 sub write_pct_config
{
1068 my ($filename, $conf) = @_;
1070 delete $conf->{snapstate
}; # just to be sure
1072 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
1073 my $used_volids = {};
1074 foreach my $vid (@$volidlist) {
1075 $used_volids->{$vid} = 1;
1078 # remove 'unusedX' settings if the volume is still used
1079 foreach my $key (keys %$conf) {
1080 my $value = $conf->{$key};
1081 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1082 delete $conf->{$key};
1086 my $generate_raw_config = sub {
1091 # add description as comment to top of file
1092 my $descr = $conf->{description
} || '';
1093 foreach my $cl (split(/\n/, $descr)) {
1094 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
1097 foreach my $key (sort keys %$conf) {
1098 next if $key eq 'digest' || $key eq 'description' ||
1099 $key eq 'pending' || $key eq 'snapshots' ||
1100 $key eq 'snapname' || $key eq 'lxc';
1101 my $value = $conf->{$key};
1102 die "detected invalid newline inside property '$key'\n"
1104 $raw .= "$key: $value\n";
1107 if (my $lxcconf = $conf->{lxc
}) {
1108 foreach my $entry (@$lxcconf) {
1109 my ($k, $v) = @$entry;
1117 my $raw = &$generate_raw_config($conf);
1119 if (scalar(keys %{$conf->{pending
}})){
1120 $raw .= "\n[pve:pending]\n";
1121 $raw .= &$generate_raw_config($conf->{pending
});
1124 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
1125 $raw .= "\n[$snapname]\n";
1126 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
1132 sub update_pct_config
{
1133 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1135 my $storage_cfg = PVE
::Storage
::config
();
1137 foreach my $opt (@$revert) {
1138 delete $conf->{pending
}->{$opt};
1139 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1142 # write updates to pending section
1143 my $modified = {}; # record modified options
1145 foreach my $opt (@$delete) {
1146 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1147 warn "cannot delete '$opt' - not set in current configuration!\n";
1150 $modified->{$opt} = 1;
1151 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1152 die "unable to delete required option '$opt'\n";
1153 } elsif ($opt =~ m/^unused(\d+)$/) {
1154 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1155 } elsif ($opt =~ m/^mp(\d+)$/) {
1156 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1157 } elsif ($opt eq 'unprivileged') {
1158 die "unable to delete read-only option: '$opt'\n";
1160 $class->add_to_pending_delete($conf, $opt);
1163 my $check_content_type = sub {
1165 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
1166 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
1167 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1168 if !$storage_config->{content
}->{rootdir
};
1171 foreach my $opt (sort keys %$param) { # add/change
1172 $modified->{$opt} = 1;
1173 my $value = $param->{$opt};
1174 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1175 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1176 my $mp = $class->parse_volume($opt, $value);
1177 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
1178 } elsif ($opt eq 'hookscript') {
1179 PVE
::GuestHelpers
::check_hookscript
($value);
1180 } elsif ($opt eq 'nameserver') {
1181 $value = PVE
::LXC
::verify_nameserver_list
($value);
1182 } elsif ($opt eq 'searchdomain') {
1183 $value = PVE
::LXC
::verify_searchdomain_list
($value);
1184 } elsif ($opt eq 'unprivileged') {
1185 die "unable to modify read-only option: '$opt'\n";
1186 } elsif ($opt eq 'tags') {
1187 $value = PVE
::GuestHelpers
::get_unique_tags
($value);
1188 } elsif ($opt =~ m/^net(\d+)$/) {
1189 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $value);
1191 if (my $mtu = $res->{mtu
}) {
1192 my $bridge_mtu = PVE
::Network
::read_bridge_mtu
($res->{bridge
});
1193 die "$opt: MTU size '$mtu' is bigger than bridge MTU '$bridge_mtu'\n"
1194 if ($mtu > $bridge_mtu);
1197 $conf->{pending
}->{$opt} = $value;
1198 $class->remove_from_pending_delete($conf, $opt);
1201 my $changes = $class->cleanup_pending($conf);
1205 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1207 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1214 my ($class, $key, $value) = @_;
1216 die "unknown setting '$key'\n" if !$confdesc->{$key};
1218 my $type = $confdesc->{$key}->{type
};
1220 if (!defined($value)) {
1221 die "got undefined value\n";
1224 if ($value =~ m/[\n\r]/) {
1225 die "property contains a line feed\n";
1228 if ($type eq 'boolean') {
1229 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1230 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1231 die "type check ('boolean') failed - got '$value'\n";
1232 } elsif ($type eq 'integer') {
1233 return int($1) if $value =~ m/^(\d+)$/;
1234 die "type check ('integer') failed - got '$value'\n";
1235 } elsif ($type eq 'number') {
1236 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1237 die "type check ('number') failed - got '$value'\n";
1238 } elsif ($type eq 'string') {
1239 if (my $fmt = $confdesc->{$key}->{format
}) {
1240 PVE
::JSONSchema
::check_format
($fmt, $value);
1245 die "internal error"
1250 # add JSON properties for create and set function
1251 sub json_config_properties
{
1252 my ($class, $prop) = @_;
1254 foreach my $opt (keys %$confdesc) {
1255 next if $opt eq 'parent' || $opt eq 'snaptime';
1256 next if $prop->{$opt};
1257 $prop->{$opt} = $confdesc->{$opt};
1263 my $parse_ct_mountpoint_full = sub {
1264 my ($class, $desc, $data, $noerr) = @_;
1269 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1271 return undef if $noerr;
1275 if (defined(my $size = $res->{size
})) {
1276 $size = PVE
::JSONSchema
::parse_size
($size);
1277 if (!defined($size)) {
1278 return undef if $noerr;
1279 die "invalid size: $size\n";
1281 $res->{size
} = $size;
1284 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1289 sub print_ct_mountpoint
{
1290 my ($class, $info, $nomp) = @_;
1291 my $skip = [ 'type' ];
1292 push @$skip, 'mp' if $nomp;
1293 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1296 sub print_ct_unused
{
1297 my ($class, $info) = @_;
1299 my $skip = [ 'type' ];
1300 return PVE
::JSONSchema
::print_property_string
($info, $unused_desc, $skip);
1304 my ($class, $key, $volume_string, $noerr) = @_;
1306 if ($key eq 'rootfs') {
1307 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1308 $res->{mp
} = '/' if defined($res);
1310 } elsif ($key =~ m/^mp\d+$/) {
1311 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1312 } elsif ($key =~ m/^unused\d+$/) {
1313 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1316 die "parse_volume - unknown type: $key\n" if !$noerr;
1322 my ($class, $device_string, $noerr) = @_;
1324 my $res = eval { PVE
::JSONSchema
::parse_property_string
($dev_desc, $device_string) };
1326 return undef if $noerr;
1330 if (!defined($res->{path
})) {
1331 return undef if $noerr;
1332 die "Path has to be defined\n";
1339 my ($class, $key, $volume) = @_;
1341 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1343 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1352 sub print_lxc_network
{
1353 my ($class, $net) = @_;
1354 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1357 sub parse_lxc_network
{
1358 my ($class, $data) = @_;
1360 return {} if !$data;
1362 my $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1364 $res->{type
} = 'veth';
1365 if (!$res->{hwaddr
}) {
1366 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1367 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1373 sub parse_features
{
1374 my ($class, $data) = @_;
1375 return {} if !$data;
1376 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1380 my ($class, $name) = @_;
1382 return defined($confdesc->{$name});
1384 # END JSON config code
1386 # takes a max memory value as KiB and returns an tuple with max and high values
1387 sub calculate_memory_constraints
{
1390 return if !defined($memory);
1392 # cgroup memory usage is limited by the hard 'max' limit (OOM-killer enforced) and the soft
1393 # 'high' limit (cgroup processes get throttled and put under heavy reclaim pressure).
1394 my $memory_max = int($memory * 1024 * 1024);
1395 # Set the high to 1016/1024 (~99.2%) of the 'max' hard limit clamped to 128 MiB max, to scale
1396 # it for the lower range while having a decent 2^x based rest for 2^y memory configs.
1397 my $memory_high = $memory >= 16 * 1024 ?
int(($memory - 128) * 1024 * 1024) : int($memory * 1024 * 1016);
1399 return ($memory_max, $memory_high);
1402 my $LXC_FASTPLUG_OPTIONS= {
1414 sub vmconfig_hotplug_pending
{
1415 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1417 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1418 my $rootdir = "/proc/$pid/root";
1420 my $add_hotplug_error = sub {
1421 my ($opt, $msg) = @_;
1422 $errors->{$opt} = "unable to hotplug $opt: $msg";
1425 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1426 next if $selection && !$selection->{$opt};
1427 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1428 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1432 my $cgroup = PVE
::LXC
::CGroup-
>new($vmid);
1434 # There's no separate swap size to configure, there's memory and "total"
1435 # memory (iow. memory+swap). This means we have to change them together.
1436 my $hotplug_memory_done;
1437 my $hotplug_memory = sub {
1438 my ($new_memory, $new_swap) = @_;
1440 ($new_memory, my $new_memory_high) = calculate_memory_constraints
($new_memory);
1441 $new_swap = int($new_swap * 1024 * 1024) if defined($new_swap);
1442 $cgroup->change_memory_limit($new_memory, $new_swap, $new_memory_high);
1444 $hotplug_memory_done = 1;
1447 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1448 # FIXME: $force deletion is not implemented for CTs
1449 foreach my $opt (sort keys %$pending_delete_hash) {
1450 next if $selection && !$selection->{$opt};
1452 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1454 } elsif ($opt =~ m/^unused(\d+)$/) {
1455 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1456 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1457 } elsif ($opt eq 'swap') {
1458 $hotplug_memory->(undef, 0);
1459 } elsif ($opt eq 'cpulimit') {
1460 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1461 } elsif ($opt eq 'cpuunits') {
1462 $cgroup->change_cpu_shares(undef);
1463 } elsif ($opt =~ m/^net(\d)$/) {
1465 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1467 my $net = PVE
::LXC
::Config-
>parse_lxc_network($conf->{$opt});
1468 print "delete ips from $opt\n";
1469 eval { PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($net->{bridge
}, $net->{hwaddr
}, $conf->{hostname
}) };
1473 die "skip\n"; # skip non-hotpluggable opts
1477 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1479 delete $conf->{$opt};
1480 $class->remove_from_pending_delete($conf, $opt);
1484 foreach my $opt (sort keys %{$conf->{pending
}}) {
1485 next if $opt eq 'delete'; # just to be sure
1486 next if $selection && !$selection->{$opt};
1487 my $value = $conf->{pending
}->{$opt};
1489 if ($opt eq 'cpulimit') {
1490 my $quota = 100000 * $value;
1491 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1492 } elsif ($opt eq 'cpuunits') {
1493 $cgroup->change_cpu_shares($value);
1494 } elsif ($opt =~ m/^net(\d+)$/) {
1496 my $net = $class->parse_lxc_network($value);
1497 $value = $class->print_lxc_network($net);
1498 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1499 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1500 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1501 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1503 } elsif ($opt =~ m/^mp(\d+)$/) {
1504 if (exists($conf->{$opt})) {
1505 die "skip\n"; # don't try to hotplug over existing mp
1508 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1509 # apply_pending_mountpoint modifies the value if it creates a new disk
1510 $value = $conf->{pending
}->{$opt};
1512 die "skip\n"; # skip non-hotpluggable
1516 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1518 $conf->{$opt} = $value;
1519 delete $conf->{pending
}->{$opt};
1524 sub vmconfig_apply_pending
{
1525 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1527 my $add_apply_error = sub {
1528 my ($opt, $msg) = @_;
1529 my $err_msg = "unable to apply pending change $opt : $msg";
1530 $errors->{$opt} = $err_msg;
1534 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1535 # FIXME: $force deletion is not implemented for CTs
1536 foreach my $opt (sort keys %$pending_delete_hash) {
1537 next if $selection && !$selection->{$opt};
1539 if ($opt =~ m/^mp(\d+)$/) {
1540 my $mp = $class->parse_volume($opt, $conf->{$opt});
1541 if ($mp->{type
} eq 'volume') {
1542 $class->add_unused_volume($conf, $mp->{volume
})
1543 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1545 } elsif ($opt =~ m/^unused(\d+)$/) {
1546 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1547 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1548 } elsif ($opt =~ m/^net(\d+)$/) {
1550 my $net = $class->parse_lxc_network($conf->{$opt});
1551 eval { PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($net->{bridge
}, $net->{hwaddr
}, $conf->{hostname
}) };
1557 $add_apply_error->($opt, $err);
1559 delete $conf->{$opt};
1560 $class->remove_from_pending_delete($conf, $opt);
1564 $class->cleanup_pending($conf);
1566 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1567 next if $opt eq 'delete'; # just to be sure
1568 next if $selection && !$selection->{$opt};
1570 if ($opt =~ m/^mp(\d+)$/) {
1571 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1572 } elsif ($opt =~ m/^net(\d+)$/) {
1574 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1575 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1577 if ($conf->{$opt}) {
1578 my $old_net = $class->parse_lxc_network($conf->{$opt});
1579 if ($old_net->{bridge
} ne $net->{bridge
} || $old_net->{hwaddr
} ne $net->{hwaddr
}) {
1580 PVE
::Network
::SDN
::Vnets
::del_ips_from_mac
($old_net->{bridge
}, $old_net->{hwaddr
}, $conf->{name
});
1581 PVE
::Network
::SDN
::Vnets
::add_next_free_cidr
($net->{bridge
}, $conf->{hostname
}, $net->{hwaddr
}, $vmid, undef, 1);
1584 PVE
::Network
::SDN
::Vnets
::add_next_free_cidr
($net->{bridge
}, $conf->{hostname
}, $net->{hwaddr
}, $vmid, undef, 1);
1590 $add_apply_error->($opt, $err);
1592 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1597 my $rescan_volume = sub {
1598 my ($storecfg, $mp) = @_;
1600 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1602 warn "Could not rescan volume size - $@\n" if $@;
1605 sub apply_pending_mountpoint
{
1606 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1608 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1609 my $old = $conf->{$opt};
1610 if ($mp->{type
} eq 'volume' && $mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1611 my $original_value = $conf->{pending
}->{$opt};
1612 my $vollist = PVE
::LXC
::create_disks
(
1615 { $opt => $original_value },
1620 # Re-parse mount point:
1621 my $mp = $class->parse_volume($opt, $conf->{pending
}->{$opt});
1623 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1627 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1628 # The pending-changes code collects errors but keeps on looping through further
1629 # pending changes, so unroll the change in $conf as well if destroy_disks()
1631 $conf->{pending
}->{$opt} = $original_value;
1636 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1637 $rescan_volume->($storecfg, $mp) if $mp->{type
} eq 'volume';
1639 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1641 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1644 if (defined($old)) {
1645 my $mp = $class->parse_volume($opt, $old);
1646 if ($mp->{type
} eq 'volume') {
1647 $class->add_unused_volume($conf, $mp->{volume
})
1648 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1653 sub classify_mountpoint
{
1654 my ($class, $vol) = @_;
1655 if ($vol =~ m!^/!) {
1656 return 'device' if $vol =~ m!^/dev/!;
1662 my $__is_volume_in_use = sub {
1663 my ($class, $config, $volid) = @_;
1666 $class->foreach_volume($config, sub {
1667 my ($ms, $mountpoint) = @_;
1669 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1675 sub is_volume_in_use_by_snapshots
{
1676 my ($class, $config, $volid) = @_;
1678 if (my $snapshots = $config->{snapshots
}) {
1679 foreach my $snap (keys %$snapshots) {
1680 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1687 sub is_volume_in_use
{
1688 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1689 return 1 if $__is_volume_in_use->($class, $config, $volid);
1690 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1691 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1695 sub has_dev_console
{
1696 my ($class, $conf) = @_;
1698 return !(defined($conf->{console
}) && !$conf->{console
});
1702 my ($class, $conf, $keyname) = @_;
1704 if (my $lxcconf = $conf->{lxc
}) {
1705 foreach my $entry (@$lxcconf) {
1706 my ($key, undef) = @$entry;
1707 return 1 if $key eq $keyname;
1715 my ($class, $conf) = @_;
1717 return $conf->{tty
} // $confdesc->{tty
}->{default};
1721 my ($class, $conf) = @_;
1723 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1726 sub valid_volume_keys
{
1727 my ($class, $reverse) = @_;
1729 my @names = ('rootfs');
1731 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1732 push @names, "mp$i";
1735 return $reverse ?
reverse @names : @names;
1738 sub valid_volume_keys_with_unused
{
1739 my ($class, $reverse) = @_;
1740 my @names = $class->valid_volume_keys();
1741 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1742 push @names, "unused$i";
1744 return $reverse ?
reverse @names : @names;
1747 sub get_vm_volumes
{
1748 my ($class, $conf, $excludes) = @_;
1752 $class->foreach_volume($conf, sub {
1753 my ($ms, $mountpoint) = @_;
1755 return if $excludes && $ms eq $excludes;
1757 my $volid = $mountpoint->{volume
};
1758 return if !$volid || $mountpoint->{type
} ne 'volume';
1760 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1763 push @$vollist, $volid;
1769 sub get_replicatable_volumes
{
1770 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1774 my $test_volid = sub {
1775 my ($volid, $mountpoint) = @_;
1779 my $mptype = $mountpoint->{type
};
1780 my $replicate = $mountpoint->{replicate
} // 1;
1782 if ($mptype ne 'volume') {
1783 # skip bindmounts if replicate = 0 even for cleanup,
1784 # since bind mounts could not have been replicated ever
1785 return if !$replicate;
1786 die "unable to replicate mountpoint type '$mptype'\n";
1789 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1790 return if !$storeid;
1792 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1793 return if $scfg->{shared
};
1795 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1796 return if !$owner || ($owner != $vmid);
1798 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1800 return if !$cleanup && !$replicate;
1802 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1803 return if $cleanup || $noerr;
1804 die "missing replicate feature on volume '$volid'\n";
1807 $volhash->{$volid} = 1;
1810 $class->foreach_volume($conf, sub {
1811 my ($ms, $mountpoint) = @_;
1812 $test_volid->($mountpoint->{volume
}, $mountpoint);
1815 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1816 my $snap = $conf->{snapshots
}->{$snapname};
1817 $class->foreach_volume($snap, sub {
1818 my ($ms, $mountpoint) = @_;
1819 $test_volid->($mountpoint->{volume
}, $mountpoint);
1823 # add 'unusedX' volumes to volhash
1824 foreach my $key (keys %$conf) {
1825 if ($key =~ m/^unused/) {
1826 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });
1833 sub get_backup_volumes
{
1834 my ($class, $conf) = @_;
1836 my $return_volumes = [];
1838 my $test_mountpoint = sub {
1839 my ($key, $volume) = @_;
1841 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1843 push @$return_volumes, {
1845 included
=> $included,
1847 volume_config
=> $volume,
1851 PVE
::LXC
::Config-
>foreach_volume($conf, $test_mountpoint);
1853 return $return_volumes;
1856 sub get_derived_property
{
1857 my ($class, $conf, $name) = @_;
1859 if ($name eq 'max-cpu') {
1860 return $conf->{cpulimit
} || $conf->{cores
} || 0;
1861 } elsif ($name eq 'max-memory') {
1862 return ($conf->{memory
} || 512) * 1024 * 1024;
1864 die "unknown derived property - $name\n";
1868 sub foreach_passthrough_device
{
1869 my ($class, $conf, $func, @param) = @_;
1871 for my $key (keys %$conf) {
1872 next if $key !~ m/^dev(\d+)$/;
1874 my $device = $class->parse_device($conf->{$key});
1876 $func->($key, $device, @param);