1 package PVE
::LXC
::Config
;
6 use PVE
::AbstractConfig
;
7 use PVE
::Cluster
qw(cfs_register_file);
8 use PVE
::DataCenterConfig
;
11 use PVE
::JSONSchema
qw(get_standard_option);
14 use base
qw(PVE::AbstractConfig);
16 my $nodename = PVE
::INotify
::nodename
();
17 my $lock_handles = {};
18 my $lockdir = "/run/lock/lxc";
20 mkdir "/etc/pve/nodes/$nodename/lxc";
21 my $MAX_MOUNT_POINTS = 256;
22 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
24 # BEGIN implemented abstract methods from PVE::AbstractConfig
30 sub __config_max_unused_disks
{
33 return $MAX_UNUSED_DISKS;
36 sub config_file_lock
{
37 my ($class, $vmid) = @_;
39 return "$lockdir/pve-config-${vmid}.lock";
43 my ($class, $vmid, $node) = @_;
45 $node = $nodename if !$node;
46 return "nodes/$node/lxc/$vmid.conf";
49 sub mountpoint_backup_enabled
{
50 my ($class, $mp_key, $mountpoint) = @_;
52 return 1 if $mp_key eq 'rootfs';
54 return 0 if $mountpoint->{type
} ne 'volume';
56 return 1 if $mountpoint->{backup
};
62 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
66 if ($feature eq 'copy' || $feature eq 'clone') {
67 $opts = {'valid_target_formats' => ['raw', 'subvol']};
70 $class->foreach_mountpoint($conf, sub {
71 my ($ms, $mountpoint) = @_;
73 return if $err; # skip further test
74 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
77 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
78 $mountpoint->{volume
},
79 $snapname, $running, $opts);
85 sub __snapshot_save_vmstate
{
86 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
87 die "implement me - snapshot_save_vmstate\n";
90 sub __snapshot_check_running
{
91 my ($class, $vmid) = @_;
92 return PVE
::LXC
::check_running
($vmid);
95 sub __snapshot_check_freeze_needed
{
96 my ($class, $vmid, $config, $save_vmstate) = @_;
98 my $ret = $class->__snapshot_check_running($vmid);
102 sub __snapshot_freeze
{
103 my ($class, $vmid, $unfreeze) = @_;
106 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
109 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
110 PVE
::LXC
::sync_container_namespace
($vmid);
114 sub __snapshot_create_vol_snapshot
{
115 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
117 my $storecfg = PVE
::Storage
::config
();
119 return if $snapname eq 'vzdump' &&
120 !$class->mountpoint_backup_enabled($ms, $mountpoint);
122 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
125 sub __snapshot_delete_remove_drive
{
126 my ($class, $snap, $remove_drive) = @_;
128 if ($remove_drive eq 'vmstate') {
129 die "implement me - saving vmstate\n";
131 my $value = $snap->{$remove_drive};
132 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
133 delete $snap->{$remove_drive};
135 $class->add_unused_volume($snap, $mountpoint->{volume
})
136 if ($mountpoint->{type
} eq 'volume');
140 sub __snapshot_delete_vmstate_file
{
141 my ($class, $snap, $force) = @_;
143 die "implement me - saving vmstate\n";
146 sub __snapshot_delete_vol_snapshot
{
147 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
149 return if $snapname eq 'vzdump' &&
150 !$class->mountpoint_backup_enabled($ms, $mountpoint);
152 my $storecfg = PVE
::Storage
::config
();
153 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
154 push @$unused, $mountpoint->{volume
};
157 sub __snapshot_rollback_vol_possible
{
158 my ($class, $mountpoint, $snapname) = @_;
160 my $storecfg = PVE
::Storage
::config
();
161 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
164 sub __snapshot_rollback_vol_rollback
{
165 my ($class, $mountpoint, $snapname) = @_;
167 my $storecfg = PVE
::Storage
::config
();
168 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
171 sub __snapshot_rollback_vm_stop
{
172 my ($class, $vmid) = @_;
174 PVE
::LXC
::vm_stop
($vmid, 1)
175 if $class->__snapshot_check_running($vmid);
178 sub __snapshot_rollback_vm_start
{
179 my ($class, $vmid, $vmstate, $data);
181 die "implement me - save vmstate\n";
184 sub __snapshot_rollback_get_unused
{
185 my ($class, $conf, $snap) = @_;
189 $class->foreach_volume($conf, sub {
190 my ($vs, $volume) = @_;
192 return if $volume->{type
} ne 'volume';
195 my $volid = $volume->{volume
};
197 $class->foreach_volume($snap, sub {
198 my ($ms, $mountpoint) = @_;
201 return if ($mountpoint->{type
} ne 'volume');
204 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
207 push @$unused, $volid if !$found;
213 # END implemented abstract methods from PVE::AbstractConfig
215 # BEGIN JSON config code
217 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
220 my $valid_mount_option_re = qr/(noatime|nodev|nosuid|noexec)/;
222 sub is_valid_mount_option
{
224 return $option =~ $valid_mount_option_re;
231 format
=> 'pve-lxc-mp-string',
232 format_description
=> 'volume',
233 description
=> 'Volume, device or directory to mount into the container.',
237 format
=> 'disk-size',
238 format_description
=> 'DiskSize',
239 description
=> 'Volume size (read only value).',
244 description
=> 'Explicitly enable or disable ACL support.',
250 description
=> 'Extra mount options for rootfs/mps.',
251 format_description
=> 'opt[;opt...]',
252 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
256 description
=> 'Read-only mount point',
261 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
266 description
=> 'Will include this volume to a storage replica job.',
272 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
273 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!",
279 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
280 type
=> 'string', format
=> $rootfs_desc,
281 description
=> "Use volume as container root.",
285 my $features_desc = {
289 description
=> "Allow mounting file systems of specific types."
290 ." This should be a list of file system types as used with the mount command."
291 ." Note that this can have negative effects on the container's security."
292 ." With access to a loop device, mounting a file can circumvent the mknod"
293 ." permission of the devices cgroup, mounting an NFS file system can"
294 ." block the host's I/O completely and prevent it from rebooting, etc.",
295 format_description
=> 'fstype;fstype;...',
296 pattern
=> qr/[a-zA-Z0-9_; ]+/,
302 description
=> "Allow nesting."
303 ." Best used with unprivileged containers with additional id mapping."
304 ." Note that this will expose procfs and sysfs contents of the host"
311 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
312 ." This is required to use docker inside a container."
313 ." By default unprivileged containers will see this system call as non-existent."
314 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
315 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
316 ." Essentially, you can choose between running systemd-networkd or docker.",
322 description
=> "Allow using 'fuse' file systems in a container."
323 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
329 description
=> "Allow unprivileged containers to use mknod() to add certain device nodes."
330 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
331 ." This is experimental.",
337 description
=> "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
338 ." This can break networking under newer (>= v245) systemd-network use."
346 description
=> "Lock/unlock the VM.",
347 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
352 description
=> "Specifies whether a VM will be started during system bootup.",
355 startup
=> get_standard_option
('pve-startup-order'),
359 description
=> "Enable/disable Template.",
365 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
366 description
=> "OS architecture type.",
372 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
373 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.",
378 description
=> "Attach a console device (/dev/console) to the container.",
384 description
=> "Specify the number of tty available to the container",
392 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
399 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.",
407 description
=> "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
415 description
=> "Amount of RAM for the VM in MB.",
422 description
=> "Amount of SWAP for the VM in MB.",
428 description
=> "Set a host name for the container.",
429 type
=> 'string', format
=> 'dns-name',
435 description
=> "Container description. Only used on the configuration web interface.",
439 type
=> 'string', format
=> 'dns-name-list',
440 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
444 type
=> 'string', format
=> 'address-list',
445 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.",
447 rootfs
=> get_standard_option
('pve-ct-rootfs'),
450 type
=> 'string', format
=> 'pve-configid',
452 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
456 description
=> "Timestamp for snapshots.",
462 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).",
464 enum
=> ['shell', 'console', 'tty'],
470 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
476 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
482 format
=> $features_desc,
483 description
=> "Allow containers access to advanced features.",
488 format
=> 'pve-volume-id',
489 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
492 type
=> 'string', format
=> 'pve-tag-list',
493 description
=> 'Tags of the Container. This is only meta information.',
498 my $valid_lxc_conf_keys = {
499 'lxc.apparmor.profile' => 1,
500 'lxc.apparmor.allow_incomplete' => 1,
501 'lxc.apparmor.allow_nesting' => 1,
502 'lxc.apparmor.raw' => 1,
503 'lxc.selinux.context' => 1,
507 'lxc.signal.halt' => 1,
508 'lxc.signal.reboot' => 1,
509 'lxc.signal.stop' => 1,
512 'lxc.console.logfile' => 1,
513 'lxc.console.path' => 1,
515 'lxc.devtty.dir' => 1,
516 'lxc.hook.autodev' => 1,
519 'lxc.mount.fstab' => 1,
520 'lxc.mount.entry' => 1,
521 'lxc.mount.auto' => 1,
522 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
523 'lxc.rootfs.mount' => 1,
524 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
525 ', please use mount point options in the "rootfs" key',
531 'lxc.seccomp.profile' => 1,
532 'lxc.seccomp.notify.proxy' => 1,
533 'lxc.seccomp.notify.cookie' => 1,
535 'lxc.hook.pre-start' => 1,
536 'lxc.hook.pre-mount' => 1,
537 'lxc.hook.mount' => 1,
538 'lxc.hook.start' => 1,
539 'lxc.hook.stop' => 1,
540 'lxc.hook.post-stop' => 1,
541 'lxc.hook.clone' => 1,
542 'lxc.hook.destroy' => 1,
543 'lxc.hook.version' => 1,
544 'lxc.log.level' => 1,
546 'lxc.start.auto' => 1,
547 'lxc.start.delay' => 1,
548 'lxc.start.order' => 1,
550 'lxc.environment' => 1,
552 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
553 'lxc.sysctl.fs.mqueue' => 1,
554 'lxc.sysctl.kernel.msgmax' => 1,
555 'lxc.sysctl.kernel.msgmnb' => 1,
556 'lxc.sysctl.kernel.msgmni' => 1,
557 'lxc.sysctl.kernel.sem' => 1,
558 'lxc.sysctl.kernel.shmall' => 1,
559 'lxc.sysctl.kernel.shmmax' => 1,
560 'lxc.sysctl.kernel.shmmni' => 1,
561 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
564 my $deprecated_lxc_conf_keys = {
565 # Deprecated (removed with lxc 3.0):
566 'lxc.aa_profile' => 'lxc.apparmor.profile',
567 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
568 'lxc.console' => 'lxc.console.path',
569 'lxc.devttydir' => 'lxc.tty.dir',
570 'lxc.haltsignal' => 'lxc.signal.halt',
571 'lxc.rebootsignal' => 'lxc.signal.reboot',
572 'lxc.stopsignal' => 'lxc.signal.stop',
573 'lxc.id_map' => 'lxc.idmap',
574 'lxc.init_cmd' => 'lxc.init.cmd',
575 'lxc.loglevel' => 'lxc.log.level',
576 'lxc.logfile' => 'lxc.log.file',
577 'lxc.mount' => 'lxc.mount.fstab',
578 'lxc.network.type' => 'lxc.net.INDEX.type',
579 'lxc.network.flags' => 'lxc.net.INDEX.flags',
580 'lxc.network.link' => 'lxc.net.INDEX.link',
581 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
582 'lxc.network.name' => 'lxc.net.INDEX.name',
583 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
584 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
585 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
586 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
587 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
588 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
589 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
590 'lxc.pts' => 'lxc.pty.max',
591 'lxc.se_context' => 'lxc.selinux.context',
592 'lxc.seccomp' => 'lxc.seccomp.profile',
593 'lxc.tty' => 'lxc.tty.max',
594 'lxc.utsname' => 'lxc.uts.name',
597 sub is_valid_lxc_conf_key
{
598 my ($vmid, $key) = @_;
599 if ($key =~ /^lxc\.limit\./) {
600 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
603 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
604 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
607 my $validity = $valid_lxc_conf_keys->{$key};
608 return $validity if defined($validity);
609 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
610 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
611 || $key =~ /^lxc\.net\./; # allow custom network definitions
615 our $netconf_desc = {
619 description
=> "Network interface type.",
624 format_description
=> 'string',
625 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
626 pattern
=> '[-_.\w\d]+',
630 format_description
=> 'bridge',
631 description
=> 'Bridge to attach the network device to.',
632 pattern
=> '[-_.\w\d]+',
635 hwaddr
=> get_standard_option
('mac-addr', {
636 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)',
640 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
641 minimum
=> 64, # minimum ethernet frame is 64 bytes
646 format
=> 'pve-ipv4-config',
647 format_description
=> '(IPv4/CIDR|dhcp|manual)',
648 description
=> 'IPv4 address in CIDR format.',
654 format_description
=> 'GatewayIPv4',
655 description
=> 'Default gateway for IPv4 traffic.',
660 format
=> 'pve-ipv6-config',
661 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
662 description
=> 'IPv6 address in CIDR format.',
668 format_description
=> 'GatewayIPv6',
669 description
=> 'Default gateway for IPv6 traffic.',
674 description
=> "Controls whether this interface's firewall rules should be used.",
681 description
=> "VLAN tag for this interface.",
686 pattern
=> qr/\d+(?:;\d+)*/,
687 format_description
=> 'vlanid[;vlanid...]',
688 description
=> "VLAN ids to pass through the interface",
693 format_description
=> 'mbps',
694 description
=> "Apply rate limiting to the interface",
698 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
700 my $MAX_LXC_NETWORKS = 10;
701 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
702 $confdesc->{"net$i"} = {
704 type
=> 'string', format
=> $netconf_desc,
705 description
=> "Specifies network interfaces for the container.",
709 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
710 sub verify_lxc_mp_string
{
711 my ($mp, $noerr) = @_;
715 # /. or /.. at the end
716 # ../ at the beginning
718 if($mp =~ m
@/\.\
.?
/@ ||
721 return undef if $noerr;
722 die "$mp contains illegal character sequences\n";
731 description
=> 'Whether to include the mount point in backups.',
732 verbose_description
=> 'Whether to include the mount point in backups '.
733 '(only used for volume mount points).',
738 format
=> 'pve-lxc-mp-string',
739 format_description
=> 'Path',
740 description
=> 'Path to the mount point as seen from inside the container '.
741 '(must not contain symlinks).',
742 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
743 "NOTE: Must not contain any symlinks for security reasons."
746 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
750 type
=> 'string', format
=> 'pve-volume-id',
751 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
754 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
755 $confdesc->{"mp$i"} = {
757 type
=> 'string', format
=> $mp_desc,
758 description
=> "Use volume as container mount point.",
763 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
764 $confdesc->{"unused$i"} = $unuseddesc;
767 sub parse_pct_config
{
768 my ($filename, $raw) = @_;
770 return undef if !defined($raw);
773 digest
=> Digest
::SHA
::sha1_hex
($raw),
778 $filename =~ m
|/lxc/(\d
+).conf
$|
779 || die "got strange filename '$filename'";
787 my @lines = split(/\n/, $raw);
788 foreach my $line (@lines) {
789 next if $line =~ m/^\s*$/;
791 if ($line =~ m/^\[pve:pending\]\s*$/i) {
792 $section = 'pending';
793 $conf->{description
} = $descr if $descr;
795 $conf = $res->{$section} = {};
797 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
799 $conf->{description
} = $descr if $descr;
801 $conf = $res->{snapshots
}->{$section} = {};
805 if ($line =~ m/^\#(.*)\s*$/) {
806 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
810 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
813 my $validity = is_valid_lxc_conf_key
($vmid, $key);
814 if ($validity eq 1) {
815 push @{$conf->{lxc
}}, [$key, $value];
816 } elsif (my $errmsg = $validity) {
817 warn "vm $vmid - $key: $errmsg\n";
819 warn "vm $vmid - unable to parse config: $line\n";
821 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
822 $descr .= PVE
::Tools
::decode_text
($2);
823 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
824 $conf->{snapstate
} = $1;
825 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
827 if ($section eq 'pending') {
828 $conf->{delete} = $value;
830 warn "vm $vmid - property 'delete' is only allowed in [pve:pending]\n";
832 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
835 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
836 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
837 $conf->{$key} = $value;
839 warn "vm $vmid - unable to parse config: $line\n";
843 $conf->{description
} = $descr if $descr;
845 delete $res->{snapstate
}; # just to be sure
850 sub write_pct_config
{
851 my ($filename, $conf) = @_;
853 delete $conf->{snapstate
}; # just to be sure
855 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
856 my $used_volids = {};
857 foreach my $vid (@$volidlist) {
858 $used_volids->{$vid} = 1;
861 # remove 'unusedX' settings if the volume is still used
862 foreach my $key (keys %$conf) {
863 my $value = $conf->{$key};
864 if ($key =~ m/^unused/ && $used_volids->{$value}) {
865 delete $conf->{$key};
869 my $generate_raw_config = sub {
874 # add description as comment to top of file
875 my $descr = $conf->{description
} || '';
876 foreach my $cl (split(/\n/, $descr)) {
877 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
880 foreach my $key (sort keys %$conf) {
881 next if $key eq 'digest' || $key eq 'description' ||
882 $key eq 'pending' || $key eq 'snapshots' ||
883 $key eq 'snapname' || $key eq 'lxc';
884 my $value = $conf->{$key};
885 die "detected invalid newline inside property '$key'\n"
887 $raw .= "$key: $value\n";
890 if (my $lxcconf = $conf->{lxc
}) {
891 foreach my $entry (@$lxcconf) {
892 my ($k, $v) = @$entry;
900 my $raw = &$generate_raw_config($conf);
902 if (scalar(keys %{$conf->{pending
}})){
903 $raw .= "\n[pve:pending]\n";
904 $raw .= &$generate_raw_config($conf->{pending
});
907 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
908 $raw .= "\n[$snapname]\n";
909 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
915 sub update_pct_config
{
916 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
918 my $storage_cfg = PVE
::Storage
::config
();
920 foreach my $opt (@$revert) {
921 delete $conf->{pending
}->{$opt};
922 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
925 # write updates to pending section
926 my $modified = {}; # record modified options
928 foreach my $opt (@$delete) {
929 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
930 warn "cannot delete '$opt' - not set in current configuration!\n";
933 $modified->{$opt} = 1;
934 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
935 die "unable to delete required option '$opt'\n";
936 } elsif ($opt =~ m/^unused(\d+)$/) {
937 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
938 } elsif ($opt =~ m/^mp(\d+)$/) {
939 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
940 } elsif ($opt eq 'unprivileged') {
941 die "unable to delete read-only option: '$opt'\n";
943 $class->add_to_pending_delete($conf, $opt);
946 my $check_content_type = sub {
948 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
949 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
950 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
951 if !$storage_config->{content
}->{rootdir
};
954 foreach my $opt (sort keys %$param) { # add/change
955 $modified->{$opt} = 1;
956 my $value = $param->{$opt};
957 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
958 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
959 my $mp = $opt eq 'rootfs' ?
$class->parse_ct_rootfs($value) : $class->parse_ct_mountpoint($value);
960 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
961 } elsif ($opt eq 'hookscript') {
962 PVE
::GuestHelpers
::check_hookscript
($value);
963 } elsif ($opt eq 'nameserver') {
964 $value = PVE
::LXC
::verify_nameserver_list
($value);
965 } elsif ($opt eq 'searchdomain') {
966 $value = PVE
::LXC
::verify_searchdomain_list
($value);
967 } elsif ($opt eq 'unprivileged') {
968 die "unable to modify read-only option: '$opt'\n";
970 $conf->{pending
}->{$opt} = $value;
971 $class->remove_from_pending_delete($conf, $opt);
974 my $changes = $class->cleanup_pending($conf);
978 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
980 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
987 my ($class, $key, $value) = @_;
989 die "unknown setting '$key'\n" if !$confdesc->{$key};
991 my $type = $confdesc->{$key}->{type
};
993 if (!defined($value)) {
994 die "got undefined value\n";
997 if ($value =~ m/[\n\r]/) {
998 die "property contains a line feed\n";
1001 if ($type eq 'boolean') {
1002 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1003 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1004 die "type check ('boolean') failed - got '$value'\n";
1005 } elsif ($type eq 'integer') {
1006 return int($1) if $value =~ m/^(\d+)$/;
1007 die "type check ('integer') failed - got '$value'\n";
1008 } elsif ($type eq 'number') {
1009 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1010 die "type check ('number') failed - got '$value'\n";
1011 } elsif ($type eq 'string') {
1012 if (my $fmt = $confdesc->{$key}->{format
}) {
1013 PVE
::JSONSchema
::check_format
($fmt, $value);
1018 die "internal error"
1023 # add JSON properties for create and set function
1024 sub json_config_properties
{
1025 my ($class, $prop) = @_;
1027 foreach my $opt (keys %$confdesc) {
1028 next if $opt eq 'parent' || $opt eq 'snaptime';
1029 next if $prop->{$opt};
1030 $prop->{$opt} = $confdesc->{$opt};
1036 sub __parse_ct_mountpoint_full
{
1037 my ($class, $desc, $data, $noerr) = @_;
1042 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1044 return undef if $noerr;
1048 if (defined(my $size = $res->{size
})) {
1049 $size = PVE
::JSONSchema
::parse_size
($size);
1050 if (!defined($size)) {
1051 return undef if $noerr;
1052 die "invalid size: $size\n";
1054 $res->{size
} = $size;
1057 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1062 sub parse_ct_rootfs
{
1063 my ($class, $data, $noerr) = @_;
1065 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1067 $res->{mp
} = '/' if defined($res);
1072 sub parse_ct_mountpoint
{
1073 my ($class, $data, $noerr) = @_;
1075 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1078 sub print_ct_mountpoint
{
1079 my ($class, $info, $nomp) = @_;
1080 my $skip = [ 'type' ];
1081 push @$skip, 'mp' if $nomp;
1082 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1086 my ($class, $key, $volume_string, $noerr) = @_;
1088 if ($key eq 'rootfs') {
1089 return $class->parse_ct_rootfs($volume_string, $noerr);
1090 } elsif ($key =~ m/^mp\d+$/ || $key =~ m/^unused\d+$/) {
1091 return $class->parse_ct_mountpoint($volume_string, $noerr);
1094 die "parse_volume - unknown type: $key\n";
1098 my ($class, $key, $volume) = @_;
1100 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1109 sub print_lxc_network
{
1110 my ($class, $net) = @_;
1111 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1114 sub parse_lxc_network
{
1115 my ($class, $data) = @_;
1119 return $res if !$data;
1121 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1123 $res->{type
} = 'veth';
1124 if (!$res->{hwaddr
}) {
1125 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1126 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1132 sub parse_features
{
1133 my ($class, $data) = @_;
1134 return {} if !$data;
1135 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1139 my ($class, $name) = @_;
1141 return defined($confdesc->{$name});
1143 # END JSON config code
1145 my $LXC_FASTPLUG_OPTIONS= {
1157 sub vmconfig_hotplug_pending
{
1158 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1160 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1161 my $rootdir = "/proc/$pid/root";
1163 my $add_hotplug_error = sub {
1164 my ($opt, $msg) = @_;
1165 $errors->{$opt} = "unable to hotplug $opt: $msg";
1169 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1170 next if $selection && !$selection->{$opt};
1171 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1172 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1178 $class->write_config($vmid, $conf);
1181 # There's no separate swap size to configure, there's memory and "total"
1182 # memory (iow. memory+swap). This means we have to change them together.
1183 my $hotplug_memory_done;
1184 my $hotplug_memory = sub {
1185 my ($wanted_memory, $wanted_swap) = @_;
1186 my $old_memory = ($conf->{memory
} || $confdesc->{memory
}->{default});
1187 my $old_swap = ($conf->{swap
} || $confdesc->{swap
}->{default});
1189 $wanted_memory //= $old_memory;
1190 $wanted_swap //= $old_swap;
1192 my $total = $wanted_memory + $wanted_swap;
1193 my $old_total = $old_memory + $old_swap;
1195 if ($total > $old_total) {
1196 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1197 "memory.memsw.limit_in_bytes",
1198 int($total*1024*1024));
1199 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1200 "memory.limit_in_bytes",
1201 int($wanted_memory*1024*1024));
1203 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1204 "memory.limit_in_bytes",
1205 int($wanted_memory*1024*1024));
1206 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1207 "memory.memsw.limit_in_bytes",
1208 int($total*1024*1024));
1210 $hotplug_memory_done = 1;
1213 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1214 # FIXME: $force deletion is not implemented for CTs
1215 foreach my $opt (sort keys %$pending_delete_hash) {
1216 next if $selection && !$selection->{$opt};
1218 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1220 } elsif ($opt =~ m/^unused(\d+)$/) {
1221 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1222 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1223 } elsif ($opt eq 'swap') {
1224 $hotplug_memory->(undef, 0);
1225 } elsif ($opt eq 'cpulimit') {
1226 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_period_us", -1);
1227 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1228 } elsif ($opt eq 'cpuunits') {
1229 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
1230 } elsif ($opt =~ m/^net(\d)$/) {
1232 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1234 die "skip\n"; # skip non-hotpluggable opts
1238 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1240 delete $conf->{$opt};
1241 $class->remove_from_pending_delete($conf, $opt);
1245 foreach my $opt (sort keys %{$conf->{pending
}}) {
1246 next if $opt eq 'delete'; # just to be sure
1247 next if $selection && !$selection->{$opt};
1248 my $value = $conf->{pending
}->{$opt};
1250 if ($opt eq 'cpulimit') {
1251 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_period_us", 100000);
1252 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1253 } elsif ($opt eq 'cpuunits') {
1254 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1255 } elsif ($opt =~ m/^net(\d+)$/) {
1257 my $net = $class->parse_lxc_network($value);
1258 $value = $class->print_lxc_network($net);
1259 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1260 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1261 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1262 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1264 } elsif ($opt =~ m/^mp(\d+)$/) {
1265 if (!PVE
::LXC
::Tools
::can_use_new_mount_api
()) {
1269 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1270 # apply_pending_mountpoint modifies the value if it creates a new disk
1271 $value = $conf->{pending
}->{$opt};
1273 die "skip\n"; # skip non-hotpluggable
1277 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1279 $conf->{$opt} = $value;
1280 delete $conf->{pending
}->{$opt};
1284 $class->write_config($vmid, $conf);
1287 sub vmconfig_apply_pending
{
1288 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1290 my $add_apply_error = sub {
1291 my ($opt, $msg) = @_;
1292 my $err_msg = "unable to apply pending change $opt : $msg";
1293 $errors->{$opt} = $err_msg;
1297 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1298 # FIXME: $force deletion is not implemented for CTs
1299 foreach my $opt (sort keys %$pending_delete_hash) {
1300 next if $selection && !$selection->{$opt};
1302 if ($opt =~ m/^mp(\d+)$/) {
1303 my $mp = $class->parse_ct_mountpoint($conf->{$opt});
1304 if ($mp->{type
} eq 'volume') {
1305 $class->add_unused_volume($conf, $mp->{volume
})
1306 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1308 } elsif ($opt =~ m/^unused(\d+)$/) {
1309 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1310 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1314 $add_apply_error->($opt, $err);
1316 delete $conf->{$opt};
1317 $class->remove_from_pending_delete($conf, $opt);
1321 $class->cleanup_pending($conf);
1323 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1324 next if $opt eq 'delete'; # just to be sure
1325 next if $selection && !$selection->{$opt};
1327 if ($opt =~ m/^mp(\d+)$/) {
1328 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1329 } elsif ($opt =~ m/^net(\d+)$/) {
1331 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1332 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1336 $add_apply_error->($opt, $err);
1338 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1342 $class->write_config($vmid, $conf);
1345 my $rescan_volume = sub {
1346 my ($storecfg, $mp) = @_;
1348 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5);
1350 warn "Could not rescan volume size - $@\n" if $@;
1353 sub apply_pending_mountpoint
{
1354 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1356 my $mp = $class->parse_ct_mountpoint($conf->{pending
}->{$opt});
1357 my $old = $conf->{$opt};
1358 if ($mp->{type
} eq 'volume') {
1359 if ($mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1360 my $original_value = $conf->{pending
}->{$opt};
1361 my $vollist = PVE
::LXC
::create_disks
(
1364 { $opt => $original_value },
1369 # Re-parse mount point:
1370 my $mp = $class->parse_ct_mountpoint($conf->{pending
}->{$opt});
1372 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1376 PVE
::LXC
::destroy_disks
($storecfg, $vollist);
1377 # The pending-changes code collects errors but keeps on looping through further
1378 # pending changes, so unroll the change in $conf as well if destroy_disks()
1380 $conf->{pending
}->{$opt} = $original_value;
1385 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1386 $rescan_volume->($storecfg, $mp);
1388 PVE
::LXC
::mountpoint_hotplug
($vmid, $conf, $opt, $mp, $storecfg);
1390 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1394 if (defined($old)) {
1395 my $mp = $class->parse_ct_mountpoint($old);
1396 if ($mp->{type
} eq 'volume') {
1397 $class->add_unused_volume($conf, $mp->{volume
})
1398 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1403 sub classify_mountpoint
{
1404 my ($class, $vol) = @_;
1405 if ($vol =~ m!^/!) {
1406 return 'device' if $vol =~ m!^/dev/!;
1412 my $__is_volume_in_use = sub {
1413 my ($class, $config, $volid) = @_;
1416 $class->foreach_mountpoint($config, sub {
1417 my ($ms, $mountpoint) = @_;
1419 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1425 sub is_volume_in_use_by_snapshots
{
1426 my ($class, $config, $volid) = @_;
1428 if (my $snapshots = $config->{snapshots
}) {
1429 foreach my $snap (keys %$snapshots) {
1430 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1437 sub is_volume_in_use
{
1438 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1439 return 1 if $__is_volume_in_use->($class, $config, $volid);
1440 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1441 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1445 sub has_dev_console
{
1446 my ($class, $conf) = @_;
1448 return !(defined($conf->{console
}) && !$conf->{console
});
1452 my ($class, $conf, $keyname) = @_;
1454 if (my $lxcconf = $conf->{lxc
}) {
1455 foreach my $entry (@$lxcconf) {
1456 my ($key, undef) = @$entry;
1457 return 1 if $key eq $keyname;
1465 my ($class, $conf) = @_;
1467 return $conf->{tty
} // $confdesc->{tty
}->{default};
1471 my ($class, $conf) = @_;
1473 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1476 sub valid_volume_keys
{
1477 my ($class, $reverse) = @_;
1479 my @names = ('rootfs');
1481 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1482 push @names, "mp$i";
1485 return $reverse ?
reverse @names : @names;
1488 sub foreach_mountpoint_full
{
1489 my ($class, $conf, $reverse, $func, @param) = @_;
1491 my $mps = [ grep { defined($conf->{$_}) } $class->valid_volume_keys($reverse) ];
1492 foreach my $key (@$mps) {
1493 my $value = $conf->{$key};
1494 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1495 next if !defined($mountpoint);
1497 &$func($key, $mountpoint, @param);
1501 sub foreach_mountpoint
{
1502 my ($class, $conf, $func, @param) = @_;
1504 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1507 sub foreach_mountpoint_reverse
{
1508 my ($class, $conf, $func, @param) = @_;
1510 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1513 sub get_vm_volumes
{
1514 my ($class, $conf, $excludes) = @_;
1518 $class->foreach_mountpoint($conf, sub {
1519 my ($ms, $mountpoint) = @_;
1521 return if $excludes && $ms eq $excludes;
1523 my $volid = $mountpoint->{volume
};
1524 return if !$volid || $mountpoint->{type
} ne 'volume';
1526 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1529 push @$vollist, $volid;
1535 sub get_replicatable_volumes
{
1536 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1540 my $test_volid = sub {
1541 my ($volid, $mountpoint) = @_;
1545 my $mptype = $mountpoint->{type
};
1546 my $replicate = $mountpoint->{replicate
} // 1;
1548 if ($mptype ne 'volume') {
1549 # skip bindmounts if replicate = 0 even for cleanup,
1550 # since bind mounts could not have been replicated ever
1551 return if !$replicate;
1552 die "unable to replicate mountpoint type '$mptype'\n";
1555 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1556 return if !$storeid;
1558 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1559 return if $scfg->{shared
};
1561 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1562 return if !$owner || ($owner != $vmid);
1564 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1566 return if !$cleanup && !$replicate;
1568 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1569 return if $cleanup || $noerr;
1570 die "missing replicate feature on volume '$volid'\n";
1573 $volhash->{$volid} = 1;
1576 $class->foreach_mountpoint($conf, sub {
1577 my ($ms, $mountpoint) = @_;
1578 $test_volid->($mountpoint->{volume
}, $mountpoint);
1581 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1582 my $snap = $conf->{snapshots
}->{$snapname};
1583 $class->foreach_mountpoint($snap, sub {
1584 my ($ms, $mountpoint) = @_;
1585 $test_volid->($mountpoint->{volume
}, $mountpoint);
1589 # add 'unusedX' volumes to volhash
1590 foreach my $key (keys %$conf) {
1591 if ($key =~ m/^unused/) {
1592 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });