1 package PVE
::LXC
::Config
;
6 use PVE
::AbstractConfig
;
7 use PVE
::Cluster
qw(cfs_register_file);
9 use PVE
::JSONSchema
qw(get_standard_option);
12 use base
qw(PVE::AbstractConfig);
14 my $nodename = PVE
::INotify
::nodename
();
15 my $lock_handles = {};
16 my $lockdir = "/run/lock/lxc";
18 mkdir "/etc/pve/nodes/$nodename/lxc";
19 my $MAX_MOUNT_POINTS = 256;
20 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
22 # BEGIN implemented abstract methods from PVE::AbstractConfig
28 sub __config_max_unused_disks
{
31 return $MAX_UNUSED_DISKS;
34 sub config_file_lock
{
35 my ($class, $vmid) = @_;
37 return "$lockdir/pve-config-${vmid}.lock";
41 my ($class, $vmid, $node) = @_;
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
47 sub mountpoint_backup_enabled
{
48 my ($class, $mp_key, $mountpoint) = @_;
50 return 1 if $mp_key eq 'rootfs';
52 return 0 if $mountpoint->{type
} ne 'volume';
54 return 1 if $mountpoint->{backup
};
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
63 $class->foreach_mountpoint($conf, sub {
64 my ($ms, $mountpoint) = @_;
66 return if $err; # skip further test
67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
70 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
71 $mountpoint->{volume
},
78 sub __snapshot_save_vmstate
{
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
83 sub __snapshot_check_running
{
84 my ($class, $vmid) = @_;
85 return PVE
::LXC
::check_running
($vmid);
88 sub __snapshot_check_freeze_needed
{
89 my ($class, $vmid, $config, $save_vmstate) = @_;
91 my $ret = $class->__snapshot_check_running($vmid);
95 sub __snapshot_freeze
{
96 my ($class, $vmid, $unfreeze) = @_;
99 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
102 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
103 PVE
::LXC
::sync_container_namespace
($vmid);
107 sub __snapshot_create_vol_snapshot
{
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
110 my $storecfg = PVE
::Storage
::config
();
112 return if $snapname eq 'vzdump' &&
113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
115 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
118 sub __snapshot_delete_remove_drive
{
119 my ($class, $snap, $remove_drive) = @_;
121 if ($remove_drive eq 'vmstate') {
122 die "implement me - saving vmstate\n";
124 my $value = $snap->{$remove_drive};
125 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
126 delete $snap->{$remove_drive};
128 $class->add_unused_volume($snap, $mountpoint->{volume
})
129 if ($mountpoint->{type
} eq 'volume');
133 sub __snapshot_delete_vmstate_file
{
134 my ($class, $snap, $force) = @_;
136 die "implement me - saving vmstate\n";
139 sub __snapshot_delete_vol_snapshot
{
140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
145 my $storecfg = PVE
::Storage
::config
();
146 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
147 push @$unused, $mountpoint->{volume
};
150 sub __snapshot_rollback_vol_possible
{
151 my ($class, $mountpoint, $snapname) = @_;
153 my $storecfg = PVE
::Storage
::config
();
154 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
157 sub __snapshot_rollback_vol_rollback
{
158 my ($class, $mountpoint, $snapname) = @_;
160 my $storecfg = PVE
::Storage
::config
();
161 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
164 sub __snapshot_rollback_vm_stop
{
165 my ($class, $vmid) = @_;
167 PVE
::LXC
::vm_stop
($vmid, 1)
168 if $class->__snapshot_check_running($vmid);
171 sub __snapshot_rollback_vm_start
{
172 my ($class, $vmid, $vmstate, $data);
174 die "implement me - save vmstate\n";
177 sub __snapshot_rollback_get_unused
{
178 my ($class, $conf, $snap) = @_;
182 $class->__snapshot_foreach_volume($conf, sub {
183 my ($vs, $volume) = @_;
185 return if $volume->{type
} ne 'volume';
188 my $volid = $volume->{volume
};
190 $class->__snapshot_foreach_volume($snap, sub {
191 my ($ms, $mountpoint) = @_;
194 return if ($mountpoint->{type
} ne 'volume');
197 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
200 push @$unused, $volid if !$found;
206 sub __snapshot_foreach_volume
{
207 my ($class, $conf, $func) = @_;
209 $class->foreach_mountpoint($conf, $func);
212 # END implemented abstract methods from PVE::AbstractConfig
214 # BEGIN JSON config code
216 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
222 format
=> 'pve-lxc-mp-string',
223 format_description
=> 'volume',
224 description
=> 'Volume, device or directory to mount into the container.',
228 format
=> 'disk-size',
229 format_description
=> 'DiskSize',
230 description
=> 'Volume size (read only value).',
235 description
=> 'Explicitly enable or disable ACL support.',
240 description
=> 'Read-only mount point',
245 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
250 description
=> 'Will include this volume to a storage replica job.',
256 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
257 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!",
263 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
264 type
=> 'string', format
=> $rootfs_desc,
265 description
=> "Use volume as container root.",
269 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
270 description
=> "The name of the snapshot.",
271 type
=> 'string', format
=> 'pve-configid',
275 my $features_desc = {
279 description
=> "Allow mounting file systems of specific types."
280 ." This should be a list of file system types as used with the mount command."
281 ." Note that this can have negative effects on the container's security."
282 ." With access to a loop device, mounting a file can circumvent the mknod"
283 ." permission of the devices cgroup, mounting an NFS file system can"
284 ." block the host's I/O completely and prevent it from rebooting, etc.",
285 format_description
=> 'fstype;fstype;...',
286 pattern
=> qr/[a-zA-Z0-9; ]+/,
292 description
=> "Allow nesting."
293 ." Best used with unprivileged containers with additional id mapping."
294 ." Note that this will expose procfs and sysfs contents of the host"
301 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
302 ." This is required to use docker inside a container."
303 ." By default unprivileged containers will see this system call as non-existent."
304 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
305 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
306 ." Essentially, you can choose between running systemd-networkd or docker.",
314 description
=> "Lock/unlock the VM.",
315 enum
=> [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
320 description
=> "Specifies whether a VM will be started during system bootup.",
323 startup
=> get_standard_option
('pve-startup-order'),
327 description
=> "Enable/disable Template.",
333 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
334 description
=> "OS architecture type.",
340 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
341 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.",
346 description
=> "Attach a console device (/dev/console) to the container.",
352 description
=> "Specify the number of tty available to the container",
360 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
367 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.",
375 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.",
383 description
=> "Amount of RAM for the VM in MB.",
390 description
=> "Amount of SWAP for the VM in MB.",
396 description
=> "Set a host name for the container.",
397 type
=> 'string', format
=> 'dns-name',
403 description
=> "Container description. Only used on the configuration web interface.",
407 type
=> 'string', format
=> 'dns-name-list',
408 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
412 type
=> 'string', format
=> 'address-list',
413 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.",
415 rootfs
=> get_standard_option
('pve-ct-rootfs'),
418 type
=> 'string', format
=> 'pve-configid',
420 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
424 description
=> "Timestamp for snapshots.",
430 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).",
432 enum
=> ['shell', 'console', 'tty'],
438 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
444 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
450 format
=> $features_desc,
451 description
=> "Allow containers access to advanced features.",
455 my $valid_lxc_conf_keys = {
456 'lxc.apparmor.profile' => 1,
457 'lxc.apparmor.allow_incomplete' => 1,
458 'lxc.apparmor.allow_nesting' => 1,
459 'lxc.apparmor.raw' => 1,
460 'lxc.selinux.context' => 1,
464 'lxc.signal.halt' => 1,
465 'lxc.signal.reboot' => 1,
466 'lxc.signal.stop' => 1,
469 'lxc.console.logfile' => 1,
470 'lxc.console.path' => 1,
472 'lxc.devtty.dir' => 1,
473 'lxc.hook.autodev' => 1,
476 'lxc.mount.fstab' => 1,
477 'lxc.mount.entry' => 1,
478 'lxc.mount.auto' => 1,
479 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
480 'lxc.rootfs.mount' => 1,
481 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
482 ', please use mount point options in the "rootfs" key',
487 'lxc.seccomp.profile' => 1,
489 'lxc.hook.pre-start' => 1,
490 'lxc.hook.pre-mount' => 1,
491 'lxc.hook.mount' => 1,
492 'lxc.hook.start' => 1,
493 'lxc.hook.stop' => 1,
494 'lxc.hook.post-stop' => 1,
495 'lxc.hook.clone' => 1,
496 'lxc.hook.destroy' => 1,
497 'lxc.log.level' => 1,
499 'lxc.start.auto' => 1,
500 'lxc.start.delay' => 1,
501 'lxc.start.order' => 1,
503 'lxc.environment' => 1,
506 my $deprecated_lxc_conf_keys = {
507 # Deprecated (removed with lxc 3.0):
508 'lxc.aa_profile' => 'lxc.apparmor.profile',
509 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
510 'lxc.console' => 'lxc.console.path',
511 'lxc.devttydir' => 'lxc.tty.dir',
512 'lxc.haltsignal' => 'lxc.signal.halt',
513 'lxc.rebootsignal' => 'lxc.signal.reboot',
514 'lxc.stopsignal' => 'lxc.signal.stop',
515 'lxc.id_map' => 'lxc.idmap',
516 'lxc.init_cmd' => 'lxc.init.cmd',
517 'lxc.loglevel' => 'lxc.log.level',
518 'lxc.logfile' => 'lxc.log.file',
519 'lxc.mount' => 'lxc.mount.fstab',
520 'lxc.network.type' => 'lxc.net.INDEX.type',
521 'lxc.network.flags' => 'lxc.net.INDEX.flags',
522 'lxc.network.link' => 'lxc.net.INDEX.link',
523 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
524 'lxc.network.name' => 'lxc.net.INDEX.name',
525 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
526 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
527 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
528 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
529 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
530 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
531 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
532 'lxc.pts' => 'lxc.pty.max',
533 'lxc.se_context' => 'lxc.selinux.context',
534 'lxc.seccomp' => 'lxc.seccomp.profile',
535 'lxc.tty' => 'lxc.tty.max',
536 'lxc.utsname' => 'lxc.uts.name',
539 sub is_valid_lxc_conf_key
{
540 my ($vmid, $key) = @_;
541 if ($key =~ /^lxc\.limit\./) {
542 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
545 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
546 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
549 my $validity = $valid_lxc_conf_keys->{$key};
550 return $validity if defined($validity);
551 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
552 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
553 || $key =~ /^lxc\.net\./; # allow custom network definitions
557 our $netconf_desc = {
561 description
=> "Network interface type.",
566 format_description
=> 'string',
567 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
568 pattern
=> '[-_.\w\d]+',
572 format_description
=> 'bridge',
573 description
=> 'Bridge to attach the network device to.',
574 pattern
=> '[-_.\w\d]+',
579 format_description
=> "XX:XX:XX:XX:XX:XX",
580 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)',
581 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
586 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
587 minimum
=> 64, # minimum ethernet frame is 64 bytes
592 format
=> 'pve-ipv4-config',
593 format_description
=> '(IPv4/CIDR|dhcp|manual)',
594 description
=> 'IPv4 address in CIDR format.',
600 format_description
=> 'GatewayIPv4',
601 description
=> 'Default gateway for IPv4 traffic.',
606 format
=> 'pve-ipv6-config',
607 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
608 description
=> 'IPv6 address in CIDR format.',
614 format_description
=> 'GatewayIPv6',
615 description
=> 'Default gateway for IPv6 traffic.',
620 description
=> "Controls whether this interface's firewall rules should be used.",
627 description
=> "VLAN tag for this interface.",
632 pattern
=> qr/\d+(?:;\d+)*/,
633 format_description
=> 'vlanid[;vlanid...]',
634 description
=> "VLAN ids to pass through the interface",
639 format_description
=> 'mbps',
640 description
=> "Apply rate limiting to the interface",
644 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
646 my $MAX_LXC_NETWORKS = 10;
647 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
648 $confdesc->{"net$i"} = {
650 type
=> 'string', format
=> $netconf_desc,
651 description
=> "Specifies network interfaces for the container.",
655 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
656 sub verify_lxc_mp_string
{
657 my ($mp, $noerr) = @_;
661 # /. or /.. at the end
662 # ../ at the beginning
664 if($mp =~ m
@/\.\
.?
/@ ||
667 return undef if $noerr;
668 die "$mp contains illegal character sequences\n";
677 description
=> 'Whether to include the mount point in backups.',
678 verbose_description
=> 'Whether to include the mount point in backups '.
679 '(only used for volume mount points).',
684 format
=> 'pve-lxc-mp-string',
685 format_description
=> 'Path',
686 description
=> 'Path to the mount point as seen from inside the container '.
687 '(must not contain symlinks).',
688 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
689 "NOTE: Must not contain any symlinks for security reasons."
692 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
696 type
=> 'string', format
=> 'pve-volume-id',
697 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
700 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
701 $confdesc->{"mp$i"} = {
703 type
=> 'string', format
=> $mp_desc,
704 description
=> "Use volume as container mount point.",
709 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
710 $confdesc->{"unused$i"} = $unuseddesc;
713 sub parse_pct_config
{
714 my ($filename, $raw) = @_;
716 return undef if !defined($raw);
719 digest
=> Digest
::SHA
::sha1_hex
($raw),
723 $filename =~ m
|/lxc/(\d
+).conf
$|
724 || die "got strange filename '$filename'";
732 my @lines = split(/\n/, $raw);
733 foreach my $line (@lines) {
734 next if $line =~ m/^\s*$/;
736 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
738 $conf->{description
} = $descr if $descr;
740 $conf = $res->{snapshots
}->{$section} = {};
744 if ($line =~ m/^\#(.*)\s*$/) {
745 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
749 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
752 my $validity = is_valid_lxc_conf_key
($vmid, $key);
753 if ($validity eq 1) {
754 push @{$conf->{lxc
}}, [$key, $value];
755 } elsif (my $errmsg = $validity) {
756 warn "vm $vmid - $key: $errmsg\n";
758 warn "vm $vmid - unable to parse config: $line\n";
760 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
761 $descr .= PVE
::Tools
::decode_text
($2);
762 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
763 $conf->{snapstate
} = $1;
764 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
767 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
768 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
769 $conf->{$key} = $value;
771 warn "vm $vmid - unable to parse config: $line\n";
775 $conf->{description
} = $descr if $descr;
777 delete $res->{snapstate
}; # just to be sure
782 sub write_pct_config
{
783 my ($filename, $conf) = @_;
785 delete $conf->{snapstate
}; # just to be sure
787 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
788 my $used_volids = {};
789 foreach my $vid (@$volidlist) {
790 $used_volids->{$vid} = 1;
793 # remove 'unusedX' settings if the volume is still used
794 foreach my $key (keys %$conf) {
795 my $value = $conf->{$key};
796 if ($key =~ m/^unused/ && $used_volids->{$value}) {
797 delete $conf->{$key};
801 my $generate_raw_config = sub {
806 # add description as comment to top of file
807 my $descr = $conf->{description
} || '';
808 foreach my $cl (split(/\n/, $descr)) {
809 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
812 foreach my $key (sort keys %$conf) {
813 next if $key eq 'digest' || $key eq 'description' ||
814 $key eq 'pending' || $key eq 'snapshots' ||
815 $key eq 'snapname' || $key eq 'lxc';
816 my $value = $conf->{$key};
817 die "detected invalid newline inside property '$key'\n"
819 $raw .= "$key: $value\n";
822 if (my $lxcconf = $conf->{lxc
}) {
823 foreach my $entry (@$lxcconf) {
824 my ($k, $v) = @$entry;
832 my $raw = &$generate_raw_config($conf);
834 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
835 $raw .= "\n[$snapname]\n";
836 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
842 sub update_pct_config
{
843 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
852 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
853 $rootdir = "/proc/$pid/root";
856 my $hotplug_error = sub {
865 if (defined($delete)) {
866 foreach my $opt (@$delete) {
867 if (!exists($conf->{$opt})) {
872 if ($opt eq 'memory' || $opt eq 'rootfs') {
873 die "unable to delete required option '$opt'\n";
874 } elsif ($opt eq 'hostname') {
875 delete $conf->{$opt};
876 } elsif ($opt eq 'swap') {
877 delete $conf->{$opt};
878 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
879 "memory.memsw.limit_in_bytes", -1);
880 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
881 delete $conf->{$opt};
882 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
883 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
884 next if $hotplug_error->($opt);
885 delete $conf->{$opt};
886 } elsif ($opt eq 'cores') {
887 delete $conf->{$opt}; # rest is handled by pvestatd
888 } elsif ($opt eq 'cpulimit') {
889 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
890 delete $conf->{$opt};
891 } elsif ($opt eq 'cpuunits') {
892 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
893 delete $conf->{$opt};
894 } elsif ($opt =~ m/^net(\d)$/) {
895 delete $conf->{$opt};
898 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
899 } elsif ($opt eq 'protection') {
900 delete $conf->{$opt};
901 } elsif ($opt =~ m/^unused(\d+)$/) {
902 next if $hotplug_error->($opt);
903 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
904 push @deleted_volumes, $conf->{$opt};
905 delete $conf->{$opt};
906 } elsif ($opt =~ m/^mp(\d+)$/) {
907 next if $hotplug_error->($opt);
908 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
909 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
910 delete $conf->{$opt};
911 if ($mp->{type
} eq 'volume') {
912 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
914 } elsif ($opt eq 'unprivileged') {
915 die "unable to delete read-only option: '$opt'\n";
916 } elsif ($opt eq 'features') {
917 next if $hotplug_error->($opt);
918 delete $conf->{$opt};
920 die "implement me (delete: $opt)"
922 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
926 # There's no separate swap size to configure, there's memory and "total"
927 # memory (iow. memory+swap). This means we have to change them together.
928 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
929 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
930 if (defined($wanted_memory) || defined($wanted_swap)) {
932 my $old_memory = ($conf->{memory
} || 512);
933 my $old_swap = ($conf->{swap
} || 0);
935 $wanted_memory //= $old_memory;
936 $wanted_swap //= $old_swap;
938 my $total = $wanted_memory + $wanted_swap;
940 my $old_total = $old_memory + $old_swap;
941 if ($total > $old_total) {
942 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
943 "memory.memsw.limit_in_bytes",
944 int($total*1024*1024));
945 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
946 "memory.limit_in_bytes",
947 int($wanted_memory*1024*1024));
949 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
950 "memory.limit_in_bytes",
951 int($wanted_memory*1024*1024));
952 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
953 "memory.memsw.limit_in_bytes",
954 int($total*1024*1024));
957 $conf->{memory
} = $wanted_memory;
958 $conf->{swap
} = $wanted_swap;
960 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
963 my $storecfg = PVE
::Storage
::config
();
965 my $used_volids = {};
966 my $check_content_type = sub {
968 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
969 my $storage_config = PVE
::Storage
::storage_config
($storecfg, $sid);
970 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
971 if !$storage_config->{content
}->{rootdir
};
974 my $rescan_volume = sub {
977 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5)
978 if !defined($mp->{size
});
980 warn "Could not rescan volume size - $@\n" if $@;
983 foreach my $opt (keys %$param) {
984 my $value = $param->{$opt};
985 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
986 if ($opt eq 'hostname' || $opt eq 'arch') {
987 $conf->{$opt} = $value;
988 } elsif ($opt eq 'onboot') {
989 $conf->{$opt} = $value ?
1 : 0;
990 } elsif ($opt eq 'startup') {
991 $conf->{$opt} = $value;
992 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
993 next if $hotplug_error->($opt);
994 $conf->{$opt} = $value;
995 } elsif ($opt eq 'nameserver') {
996 next if $hotplug_error->($opt);
997 my $list = PVE
::LXC
::verify_nameserver_list
($value);
998 $conf->{$opt} = $list;
999 } elsif ($opt eq 'searchdomain') {
1000 next if $hotplug_error->($opt);
1001 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
1002 $conf->{$opt} = $list;
1003 } elsif ($opt eq 'cores') {
1004 $conf->{$opt} = $value;# rest is handled by pvestatd
1005 } elsif ($opt eq 'cpulimit') {
1007 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1009 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1011 $conf->{$opt} = $value;
1012 } elsif ($opt eq 'cpuunits') {
1013 $conf->{$opt} = $value;
1014 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1015 } elsif ($opt eq 'description') {
1016 $conf->{$opt} = $value;
1017 } elsif ($opt =~ m/^net(\d+)$/) {
1019 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1021 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1023 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1025 } elsif ($opt eq 'protection') {
1026 $conf->{$opt} = $value ?
1 : 0;
1027 } elsif ($opt =~ m/^mp(\d+)$/) {
1028 next if $hotplug_error->($opt);
1029 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
1030 my $old = $conf->{$opt};
1031 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1032 if ($mp->{type
} eq 'volume') {
1033 &$check_content_type($mp);
1034 $used_volids->{$mp->{volume
}} = 1;
1035 &$rescan_volume($mp);
1036 $conf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp);
1038 $conf->{$opt} = $value;
1040 if (defined($old)) {
1041 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
1042 if ($mp->{type
} eq 'volume') {
1043 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1047 } elsif ($opt eq 'rootfs') {
1048 next if $hotplug_error->($opt);
1049 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
1050 my $old = $conf->{$opt};
1051 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
1052 if ($mp->{type
} eq 'volume') {
1053 &$check_content_type($mp);
1054 $used_volids->{$mp->{volume
}} = 1;
1055 &$rescan_volume($mp);
1056 $conf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, 1);
1058 $conf->{$opt} = $value;
1060 if (defined($old)) {
1061 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
1062 if ($mp->{type
} eq 'volume') {
1063 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1067 } elsif ($opt eq 'unprivileged') {
1068 die "unable to modify read-only option: '$opt'\n";
1069 } elsif ($opt eq 'ostype') {
1070 next if $hotplug_error->($opt);
1071 $conf->{$opt} = $value;
1072 } elsif ($opt eq 'features') {
1073 next if $hotplug_error->($opt);
1074 $conf->{$opt} = $value;
1076 die "implement me: $opt";
1079 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1082 # Apply deletions and creations of new volumes
1083 if (@deleted_volumes) {
1084 my $storage_cfg = PVE
::Storage
::config
();
1085 foreach my $volume (@deleted_volumes) {
1086 next if $used_volids->{$volume}; # could have been re-added, too
1087 # also check for references in snapshots
1088 next if $class->is_volume_in_use($conf, $volume, 1);
1089 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1094 my $storage_cfg = PVE
::Storage
::config
();
1095 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
1098 # This should be the last thing we do here
1099 if ($running && scalar(@nohotplug)) {
1100 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1105 my ($class, $key, $value) = @_;
1107 die "unknown setting '$key'\n" if !$confdesc->{$key};
1109 my $type = $confdesc->{$key}->{type
};
1111 if (!defined($value)) {
1112 die "got undefined value\n";
1115 if ($value =~ m/[\n\r]/) {
1116 die "property contains a line feed\n";
1119 if ($type eq 'boolean') {
1120 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1121 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1122 die "type check ('boolean') failed - got '$value'\n";
1123 } elsif ($type eq 'integer') {
1124 return int($1) if $value =~ m/^(\d+)$/;
1125 die "type check ('integer') failed - got '$value'\n";
1126 } elsif ($type eq 'number') {
1127 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1128 die "type check ('number') failed - got '$value'\n";
1129 } elsif ($type eq 'string') {
1130 if (my $fmt = $confdesc->{$key}->{format
}) {
1131 PVE
::JSONSchema
::check_format
($fmt, $value);
1136 die "internal error"
1141 # add JSON properties for create and set function
1142 sub json_config_properties
{
1143 my ($class, $prop) = @_;
1145 foreach my $opt (keys %$confdesc) {
1146 next if $opt eq 'parent' || $opt eq 'snaptime';
1147 next if $prop->{$opt};
1148 $prop->{$opt} = $confdesc->{$opt};
1154 sub __parse_ct_mountpoint_full
{
1155 my ($class, $desc, $data, $noerr) = @_;
1160 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1162 return undef if $noerr;
1166 if (defined(my $size = $res->{size
})) {
1167 $size = PVE
::JSONSchema
::parse_size
($size);
1168 if (!defined($size)) {
1169 return undef if $noerr;
1170 die "invalid size: $size\n";
1172 $res->{size
} = $size;
1175 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1180 sub parse_ct_rootfs
{
1181 my ($class, $data, $noerr) = @_;
1183 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1185 $res->{mp
} = '/' if defined($res);
1190 sub parse_ct_mountpoint
{
1191 my ($class, $data, $noerr) = @_;
1193 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1196 sub print_ct_mountpoint
{
1197 my ($class, $info, $nomp) = @_;
1198 my $skip = [ 'type' ];
1199 push @$skip, 'mp' if $nomp;
1200 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1203 sub print_lxc_network
{
1204 my ($class, $net) = @_;
1205 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1208 sub parse_lxc_network
{
1209 my ($class, $data) = @_;
1213 return $res if !$data;
1215 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1217 $res->{type
} = 'veth';
1218 if (!$res->{hwaddr
}) {
1219 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1220 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1226 sub parse_features
{
1227 my ($class, $data) = @_;
1228 return {} if !$data;
1229 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1233 my ($class, $name) = @_;
1235 return defined($confdesc->{$name});
1237 # END JSON config code
1239 sub classify_mountpoint
{
1240 my ($class, $vol) = @_;
1241 if ($vol =~ m!^/!) {
1242 return 'device' if $vol =~ m!^/dev/!;
1248 my $is_volume_in_use = sub {
1249 my ($class, $config, $volid) = @_;
1252 $class->foreach_mountpoint($config, sub {
1253 my ($ms, $mountpoint) = @_;
1255 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1261 sub is_volume_in_use_by_snapshots
{
1262 my ($class, $config, $volid) = @_;
1264 if (my $snapshots = $config->{snapshots
}) {
1265 foreach my $snap (keys %$snapshots) {
1266 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1273 sub is_volume_in_use
{
1274 my ($class, $config, $volid, $include_snapshots) = @_;
1275 return 1 if $is_volume_in_use->($class, $config, $volid);
1276 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1280 sub has_dev_console
{
1281 my ($class, $conf) = @_;
1283 return !(defined($conf->{console
}) && !$conf->{console
});
1287 my ($class, $conf, $keyname) = @_;
1289 if (my $lxcconf = $conf->{lxc
}) {
1290 foreach my $entry (@$lxcconf) {
1291 my ($key, undef) = @$entry;
1292 return 1 if $key eq $keyname;
1300 my ($class, $conf) = @_;
1302 return $conf->{tty
} // $confdesc->{tty
}->{default};
1306 my ($class, $conf) = @_;
1308 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1311 sub mountpoint_names
{
1312 my ($class, $reverse) = @_;
1314 my @names = ('rootfs');
1316 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1317 push @names, "mp$i";
1320 return $reverse ?
reverse @names : @names;
1323 sub foreach_mountpoint_full
{
1324 my ($class, $conf, $reverse, $func, @param) = @_;
1326 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1327 foreach my $key (@$mps) {
1328 my $value = $conf->{$key};
1329 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1330 next if !defined($mountpoint);
1332 &$func($key, $mountpoint, @param);
1336 sub foreach_mountpoint
{
1337 my ($class, $conf, $func, @param) = @_;
1339 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1342 sub foreach_mountpoint_reverse
{
1343 my ($class, $conf, $func, @param) = @_;
1345 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1348 sub get_vm_volumes
{
1349 my ($class, $conf, $excludes) = @_;
1353 $class->foreach_mountpoint($conf, sub {
1354 my ($ms, $mountpoint) = @_;
1356 return if $excludes && $ms eq $excludes;
1358 my $volid = $mountpoint->{volume
};
1359 return if !$volid || $mountpoint->{type
} ne 'volume';
1361 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1364 push @$vollist, $volid;
1370 sub get_replicatable_volumes
{
1371 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1375 my $test_volid = sub {
1376 my ($volid, $mountpoint) = @_;
1380 my $mptype = $mountpoint->{type
};
1381 my $replicate = $mountpoint->{replicate
} // 1;
1383 if ($mptype ne 'volume') {
1384 # skip bindmounts if replicate = 0 even for cleanup,
1385 # since bind mounts could not have been replicated ever
1386 return if !$replicate;
1387 die "unable to replicate mountpoint type '$mptype'\n";
1390 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1391 return if !$storeid;
1393 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1394 return if $scfg->{shared
};
1396 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1397 return if !$owner || ($owner != $vmid);
1399 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1401 return if !$cleanup && !$replicate;
1403 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1404 return if $cleanup || $noerr;
1405 die "missing replicate feature on volume '$volid'\n";
1408 $volhash->{$volid} = 1;
1411 $class->foreach_mountpoint($conf, sub {
1412 my ($ms, $mountpoint) = @_;
1413 $test_volid->($mountpoint->{volume
}, $mountpoint);
1416 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1417 my $snap = $conf->{snapshots
}->{$snapname};
1418 $class->foreach_mountpoint($snap, sub {
1419 my ($ms, $mountpoint) = @_;
1420 $test_volid->($mountpoint->{volume
}, $mountpoint);
1424 # add 'unusedX' volumes to volhash
1425 foreach my $key (keys %$conf) {
1426 if ($key =~ m/^unused/) {
1427 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });