1 package PVE
::LXC
::Config
;
6 use PVE
::AbstractConfig
;
7 use PVE
::Cluster
qw(cfs_register_file);
10 use PVE
::JSONSchema
qw(get_standard_option);
13 use base
qw(PVE::AbstractConfig);
15 my $nodename = PVE
::INotify
::nodename
();
16 my $lock_handles = {};
17 my $lockdir = "/run/lock/lxc";
19 mkdir "/etc/pve/nodes/$nodename/lxc";
20 my $MAX_MOUNT_POINTS = 256;
21 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
23 # BEGIN implemented abstract methods from PVE::AbstractConfig
29 sub __config_max_unused_disks
{
32 return $MAX_UNUSED_DISKS;
35 sub config_file_lock
{
36 my ($class, $vmid) = @_;
38 return "$lockdir/pve-config-${vmid}.lock";
42 my ($class, $vmid, $node) = @_;
44 $node = $nodename if !$node;
45 return "nodes/$node/lxc/$vmid.conf";
48 sub mountpoint_backup_enabled
{
49 my ($class, $mp_key, $mountpoint) = @_;
51 return 1 if $mp_key eq 'rootfs';
53 return 0 if $mountpoint->{type
} ne 'volume';
55 return 1 if $mountpoint->{backup
};
61 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
64 $class->foreach_mountpoint($conf, sub {
65 my ($ms, $mountpoint) = @_;
67 return if $err; # skip further test
68 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
71 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
72 $mountpoint->{volume
},
79 sub __snapshot_save_vmstate
{
80 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
81 die "implement me - snapshot_save_vmstate\n";
84 sub __snapshot_check_running
{
85 my ($class, $vmid) = @_;
86 return PVE
::LXC
::check_running
($vmid);
89 sub __snapshot_check_freeze_needed
{
90 my ($class, $vmid, $config, $save_vmstate) = @_;
92 my $ret = $class->__snapshot_check_running($vmid);
96 sub __snapshot_freeze
{
97 my ($class, $vmid, $unfreeze) = @_;
100 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
103 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
104 PVE
::LXC
::sync_container_namespace
($vmid);
108 sub __snapshot_create_vol_snapshot
{
109 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
111 my $storecfg = PVE
::Storage
::config
();
113 return if $snapname eq 'vzdump' &&
114 !$class->mountpoint_backup_enabled($ms, $mountpoint);
116 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
119 sub __snapshot_delete_remove_drive
{
120 my ($class, $snap, $remove_drive) = @_;
122 if ($remove_drive eq 'vmstate') {
123 die "implement me - saving vmstate\n";
125 my $value = $snap->{$remove_drive};
126 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
127 delete $snap->{$remove_drive};
129 $class->add_unused_volume($snap, $mountpoint->{volume
})
130 if ($mountpoint->{type
} eq 'volume');
134 sub __snapshot_delete_vmstate_file
{
135 my ($class, $snap, $force) = @_;
137 die "implement me - saving vmstate\n";
140 sub __snapshot_delete_vol_snapshot
{
141 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
143 return if $snapname eq 'vzdump' &&
144 !$class->mountpoint_backup_enabled($ms, $mountpoint);
146 my $storecfg = PVE
::Storage
::config
();
147 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
148 push @$unused, $mountpoint->{volume
};
151 sub __snapshot_rollback_vol_possible
{
152 my ($class, $mountpoint, $snapname) = @_;
154 my $storecfg = PVE
::Storage
::config
();
155 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
158 sub __snapshot_rollback_vol_rollback
{
159 my ($class, $mountpoint, $snapname) = @_;
161 my $storecfg = PVE
::Storage
::config
();
162 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
165 sub __snapshot_rollback_vm_stop
{
166 my ($class, $vmid) = @_;
168 PVE
::LXC
::vm_stop
($vmid, 1)
169 if $class->__snapshot_check_running($vmid);
172 sub __snapshot_rollback_vm_start
{
173 my ($class, $vmid, $vmstate, $data);
175 die "implement me - save vmstate\n";
178 sub __snapshot_rollback_get_unused
{
179 my ($class, $conf, $snap) = @_;
183 $class->__snapshot_foreach_volume($conf, sub {
184 my ($vs, $volume) = @_;
186 return if $volume->{type
} ne 'volume';
189 my $volid = $volume->{volume
};
191 $class->__snapshot_foreach_volume($snap, sub {
192 my ($ms, $mountpoint) = @_;
195 return if ($mountpoint->{type
} ne 'volume');
198 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
201 push @$unused, $volid if !$found;
207 sub __snapshot_foreach_volume
{
208 my ($class, $conf, $func) = @_;
210 $class->foreach_mountpoint($conf, $func);
213 # END implemented abstract methods from PVE::AbstractConfig
215 # BEGIN JSON config code
217 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
223 format
=> 'pve-lxc-mp-string',
224 format_description
=> 'volume',
225 description
=> 'Volume, device or directory to mount into the container.',
229 format
=> 'disk-size',
230 format_description
=> 'DiskSize',
231 description
=> 'Volume size (read only value).',
236 description
=> 'Explicitly enable or disable ACL support.',
241 description
=> 'Read-only mount point',
246 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
251 description
=> 'Will include this volume to a storage replica job.',
257 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
258 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!",
264 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
265 type
=> 'string', format
=> $rootfs_desc,
266 description
=> "Use volume as container root.",
270 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
271 description
=> "The name of the snapshot.",
272 type
=> 'string', format
=> 'pve-configid',
276 my $features_desc = {
280 description
=> "Allow mounting file systems of specific types."
281 ." This should be a list of file system types as used with the mount command."
282 ." Note that this can have negative effects on the container's security."
283 ." With access to a loop device, mounting a file can circumvent the mknod"
284 ." permission of the devices cgroup, mounting an NFS file system can"
285 ." block the host's I/O completely and prevent it from rebooting, etc.",
286 format_description
=> 'fstype;fstype;...',
287 pattern
=> qr/[a-zA-Z0-9; ]+/,
293 description
=> "Allow nesting."
294 ." Best used with unprivileged containers with additional id mapping."
295 ." Note that this will expose procfs and sysfs contents of the host"
302 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
303 ." This is required to use docker inside a container."
304 ." By default unprivileged containers will see this system call as non-existent."
305 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
306 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
307 ." Essentially, you can choose between running systemd-networkd or docker.",
313 description
=> "Allow using 'fuse' file systems in a container."
314 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
322 description
=> "Lock/unlock the VM.",
323 enum
=> [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
328 description
=> "Specifies whether a VM will be started during system bootup.",
331 startup
=> get_standard_option
('pve-startup-order'),
335 description
=> "Enable/disable Template.",
341 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
342 description
=> "OS architecture type.",
348 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
349 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.",
354 description
=> "Attach a console device (/dev/console) to the container.",
360 description
=> "Specify the number of tty available to the container",
368 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
375 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.",
383 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.",
391 description
=> "Amount of RAM for the VM in MB.",
398 description
=> "Amount of SWAP for the VM in MB.",
404 description
=> "Set a host name for the container.",
405 type
=> 'string', format
=> 'dns-name',
411 description
=> "Container description. Only used on the configuration web interface.",
415 type
=> 'string', format
=> 'dns-name-list',
416 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
420 type
=> 'string', format
=> 'address-list',
421 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.",
423 rootfs
=> get_standard_option
('pve-ct-rootfs'),
426 type
=> 'string', format
=> 'pve-configid',
428 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
432 description
=> "Timestamp for snapshots.",
438 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).",
440 enum
=> ['shell', 'console', 'tty'],
446 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
452 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
458 format
=> $features_desc,
459 description
=> "Allow containers access to advanced features.",
464 format
=> 'pve-volume-id',
465 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
469 my $valid_lxc_conf_keys = {
470 'lxc.apparmor.profile' => 1,
471 'lxc.apparmor.allow_incomplete' => 1,
472 'lxc.apparmor.allow_nesting' => 1,
473 'lxc.apparmor.raw' => 1,
474 'lxc.selinux.context' => 1,
478 'lxc.signal.halt' => 1,
479 'lxc.signal.reboot' => 1,
480 'lxc.signal.stop' => 1,
483 'lxc.console.logfile' => 1,
484 'lxc.console.path' => 1,
486 'lxc.devtty.dir' => 1,
487 'lxc.hook.autodev' => 1,
490 'lxc.mount.fstab' => 1,
491 'lxc.mount.entry' => 1,
492 'lxc.mount.auto' => 1,
493 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
494 'lxc.rootfs.mount' => 1,
495 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
496 ', please use mount point options in the "rootfs" key',
502 'lxc.seccomp.profile' => 1,
504 'lxc.hook.pre-start' => 1,
505 'lxc.hook.pre-mount' => 1,
506 'lxc.hook.mount' => 1,
507 'lxc.hook.start' => 1,
508 'lxc.hook.stop' => 1,
509 'lxc.hook.post-stop' => 1,
510 'lxc.hook.clone' => 1,
511 'lxc.hook.destroy' => 1,
512 'lxc.log.level' => 1,
514 'lxc.start.auto' => 1,
515 'lxc.start.delay' => 1,
516 'lxc.start.order' => 1,
518 'lxc.environment' => 1,
520 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
521 'lxc.sysctl.fs.mqueue' => 1,
522 'lxc.sysctl.kernel.msgmax' => 1,
523 'lxc.sysctl.kernel.msgmnb' => 1,
524 'lxc.sysctl.kernel.msgmni' => 1,
525 'lxc.sysctl.kernel.sem' => 1,
526 'lxc.sysctl.kernel.shmall' => 1,
527 'lxc.sysctl.kernel.shmmax' => 1,
528 'lxc.sysctl.kernel.shmmni' => 1,
529 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
532 my $deprecated_lxc_conf_keys = {
533 # Deprecated (removed with lxc 3.0):
534 'lxc.aa_profile' => 'lxc.apparmor.profile',
535 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
536 'lxc.console' => 'lxc.console.path',
537 'lxc.devttydir' => 'lxc.tty.dir',
538 'lxc.haltsignal' => 'lxc.signal.halt',
539 'lxc.rebootsignal' => 'lxc.signal.reboot',
540 'lxc.stopsignal' => 'lxc.signal.stop',
541 'lxc.id_map' => 'lxc.idmap',
542 'lxc.init_cmd' => 'lxc.init.cmd',
543 'lxc.loglevel' => 'lxc.log.level',
544 'lxc.logfile' => 'lxc.log.file',
545 'lxc.mount' => 'lxc.mount.fstab',
546 'lxc.network.type' => 'lxc.net.INDEX.type',
547 'lxc.network.flags' => 'lxc.net.INDEX.flags',
548 'lxc.network.link' => 'lxc.net.INDEX.link',
549 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
550 'lxc.network.name' => 'lxc.net.INDEX.name',
551 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
552 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
553 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
554 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
555 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
556 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
557 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
558 'lxc.pts' => 'lxc.pty.max',
559 'lxc.se_context' => 'lxc.selinux.context',
560 'lxc.seccomp' => 'lxc.seccomp.profile',
561 'lxc.tty' => 'lxc.tty.max',
562 'lxc.utsname' => 'lxc.uts.name',
565 sub is_valid_lxc_conf_key
{
566 my ($vmid, $key) = @_;
567 if ($key =~ /^lxc\.limit\./) {
568 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
571 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
572 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
575 my $validity = $valid_lxc_conf_keys->{$key};
576 return $validity if defined($validity);
577 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
578 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
579 || $key =~ /^lxc\.net\./; # allow custom network definitions
583 our $netconf_desc = {
587 description
=> "Network interface type.",
592 format_description
=> 'string',
593 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
594 pattern
=> '[-_.\w\d]+',
598 format_description
=> 'bridge',
599 description
=> 'Bridge to attach the network device to.',
600 pattern
=> '[-_.\w\d]+',
605 format_description
=> "XX:XX:XX:XX:XX:XX",
606 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)',
607 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
612 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
613 minimum
=> 64, # minimum ethernet frame is 64 bytes
618 format
=> 'pve-ipv4-config',
619 format_description
=> '(IPv4/CIDR|dhcp|manual)',
620 description
=> 'IPv4 address in CIDR format.',
626 format_description
=> 'GatewayIPv4',
627 description
=> 'Default gateway for IPv4 traffic.',
632 format
=> 'pve-ipv6-config',
633 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
634 description
=> 'IPv6 address in CIDR format.',
640 format_description
=> 'GatewayIPv6',
641 description
=> 'Default gateway for IPv6 traffic.',
646 description
=> "Controls whether this interface's firewall rules should be used.",
653 description
=> "VLAN tag for this interface.",
658 pattern
=> qr/\d+(?:;\d+)*/,
659 format_description
=> 'vlanid[;vlanid...]',
660 description
=> "VLAN ids to pass through the interface",
665 format_description
=> 'mbps',
666 description
=> "Apply rate limiting to the interface",
670 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
672 my $MAX_LXC_NETWORKS = 10;
673 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
674 $confdesc->{"net$i"} = {
676 type
=> 'string', format
=> $netconf_desc,
677 description
=> "Specifies network interfaces for the container.",
681 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
682 sub verify_lxc_mp_string
{
683 my ($mp, $noerr) = @_;
687 # /. or /.. at the end
688 # ../ at the beginning
690 if($mp =~ m
@/\.\
.?
/@ ||
693 return undef if $noerr;
694 die "$mp contains illegal character sequences\n";
703 description
=> 'Whether to include the mount point in backups.',
704 verbose_description
=> 'Whether to include the mount point in backups '.
705 '(only used for volume mount points).',
710 format
=> 'pve-lxc-mp-string',
711 format_description
=> 'Path',
712 description
=> 'Path to the mount point as seen from inside the container '.
713 '(must not contain symlinks).',
714 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
715 "NOTE: Must not contain any symlinks for security reasons."
718 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
722 type
=> 'string', format
=> 'pve-volume-id',
723 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
726 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
727 $confdesc->{"mp$i"} = {
729 type
=> 'string', format
=> $mp_desc,
730 description
=> "Use volume as container mount point.",
735 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
736 $confdesc->{"unused$i"} = $unuseddesc;
739 sub parse_pct_config
{
740 my ($filename, $raw) = @_;
742 return undef if !defined($raw);
745 digest
=> Digest
::SHA
::sha1_hex
($raw),
749 $filename =~ m
|/lxc/(\d
+).conf
$|
750 || die "got strange filename '$filename'";
758 my @lines = split(/\n/, $raw);
759 foreach my $line (@lines) {
760 next if $line =~ m/^\s*$/;
762 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
764 $conf->{description
} = $descr if $descr;
766 $conf = $res->{snapshots
}->{$section} = {};
770 if ($line =~ m/^\#(.*)\s*$/) {
771 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
775 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
778 my $validity = is_valid_lxc_conf_key
($vmid, $key);
779 if ($validity eq 1) {
780 push @{$conf->{lxc
}}, [$key, $value];
781 } elsif (my $errmsg = $validity) {
782 warn "vm $vmid - $key: $errmsg\n";
784 warn "vm $vmid - unable to parse config: $line\n";
786 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
787 $descr .= PVE
::Tools
::decode_text
($2);
788 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
789 $conf->{snapstate
} = $1;
790 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
793 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
794 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
795 $conf->{$key} = $value;
797 warn "vm $vmid - unable to parse config: $line\n";
801 $conf->{description
} = $descr if $descr;
803 delete $res->{snapstate
}; # just to be sure
808 sub write_pct_config
{
809 my ($filename, $conf) = @_;
811 delete $conf->{snapstate
}; # just to be sure
813 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
814 my $used_volids = {};
815 foreach my $vid (@$volidlist) {
816 $used_volids->{$vid} = 1;
819 # remove 'unusedX' settings if the volume is still used
820 foreach my $key (keys %$conf) {
821 my $value = $conf->{$key};
822 if ($key =~ m/^unused/ && $used_volids->{$value}) {
823 delete $conf->{$key};
827 my $generate_raw_config = sub {
832 # add description as comment to top of file
833 my $descr = $conf->{description
} || '';
834 foreach my $cl (split(/\n/, $descr)) {
835 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
838 foreach my $key (sort keys %$conf) {
839 next if $key eq 'digest' || $key eq 'description' ||
840 $key eq 'pending' || $key eq 'snapshots' ||
841 $key eq 'snapname' || $key eq 'lxc';
842 my $value = $conf->{$key};
843 die "detected invalid newline inside property '$key'\n"
845 $raw .= "$key: $value\n";
848 if (my $lxcconf = $conf->{lxc
}) {
849 foreach my $entry (@$lxcconf) {
850 my ($k, $v) = @$entry;
858 my $raw = &$generate_raw_config($conf);
860 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
861 $raw .= "\n[$snapname]\n";
862 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
868 sub update_pct_config
{
869 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
878 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
879 $rootdir = "/proc/$pid/root";
882 my $hotplug_error = sub {
891 if (defined($delete)) {
892 foreach my $opt (@$delete) {
893 if (!exists($conf->{$opt})) {
898 if ($opt eq 'memory' || $opt eq 'rootfs') {
899 die "unable to delete required option '$opt'\n";
900 } elsif ($opt eq 'hostname') {
901 delete $conf->{$opt};
902 } elsif ($opt eq 'swap') {
903 delete $conf->{$opt};
904 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
905 "memory.memsw.limit_in_bytes", -1);
906 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup' || $opt eq 'hookscript') {
907 delete $conf->{$opt};
908 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
909 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
910 next if $hotplug_error->($opt);
911 delete $conf->{$opt};
912 } elsif ($opt eq 'cores') {
913 delete $conf->{$opt}; # rest is handled by pvestatd
914 } elsif ($opt eq 'cpulimit') {
915 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
916 delete $conf->{$opt};
917 } elsif ($opt eq 'cpuunits') {
918 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
919 delete $conf->{$opt};
920 } elsif ($opt =~ m/^net(\d)$/) {
921 delete $conf->{$opt};
924 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
925 } elsif ($opt eq 'protection') {
926 delete $conf->{$opt};
927 } elsif ($opt =~ m/^unused(\d+)$/) {
928 next if $hotplug_error->($opt);
929 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
930 push @deleted_volumes, $conf->{$opt};
931 delete $conf->{$opt};
932 } elsif ($opt =~ m/^mp(\d+)$/) {
933 next if $hotplug_error->($opt);
934 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
935 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
936 delete $conf->{$opt};
937 if ($mp->{type
} eq 'volume') {
938 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
940 } elsif ($opt eq 'unprivileged') {
941 die "unable to delete read-only option: '$opt'\n";
942 } elsif ($opt eq 'features') {
943 next if $hotplug_error->($opt);
944 delete $conf->{$opt};
946 die "implement me (delete: $opt)"
948 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
952 # There's no separate swap size to configure, there's memory and "total"
953 # memory (iow. memory+swap). This means we have to change them together.
954 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
955 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
956 if (defined($wanted_memory) || defined($wanted_swap)) {
958 my $old_memory = ($conf->{memory
} || 512);
959 my $old_swap = ($conf->{swap
} || 0);
961 $wanted_memory //= $old_memory;
962 $wanted_swap //= $old_swap;
964 my $total = $wanted_memory + $wanted_swap;
966 my $old_total = $old_memory + $old_swap;
967 if ($total > $old_total) {
968 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
969 "memory.memsw.limit_in_bytes",
970 int($total*1024*1024));
971 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
972 "memory.limit_in_bytes",
973 int($wanted_memory*1024*1024));
975 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
976 "memory.limit_in_bytes",
977 int($wanted_memory*1024*1024));
978 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
979 "memory.memsw.limit_in_bytes",
980 int($total*1024*1024));
983 $conf->{memory
} = $wanted_memory;
984 $conf->{swap
} = $wanted_swap;
986 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
989 my $storecfg = PVE
::Storage
::config
();
991 my $used_volids = {};
992 my $check_content_type = sub {
994 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
995 my $storage_config = PVE
::Storage
::storage_config
($storecfg, $sid);
996 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
997 if !$storage_config->{content
}->{rootdir
};
1000 my $rescan_volume = sub {
1003 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5)
1004 if !defined($mp->{size
});
1006 warn "Could not rescan volume size - $@\n" if $@;
1009 foreach my $opt (keys %$param) {
1010 my $value = $param->{$opt};
1011 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
1012 if ($opt eq 'hostname' || $opt eq 'arch') {
1013 $conf->{$opt} = $value;
1014 } elsif ($opt eq 'onboot') {
1015 $conf->{$opt} = $value ?
1 : 0;
1016 } elsif ($opt eq 'startup') {
1017 $conf->{$opt} = $value;
1018 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1019 next if $hotplug_error->($opt);
1020 $conf->{$opt} = $value;
1021 } elsif ($opt eq 'nameserver') {
1022 next if $hotplug_error->($opt);
1023 my $list = PVE
::LXC
::verify_nameserver_list
($value);
1024 $conf->{$opt} = $list;
1025 } elsif ($opt eq 'searchdomain') {
1026 next if $hotplug_error->($opt);
1027 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
1028 $conf->{$opt} = $list;
1029 } elsif ($opt eq 'cores') {
1030 $conf->{$opt} = $value;# rest is handled by pvestatd
1031 } elsif ($opt eq 'cpulimit') {
1033 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1035 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1037 $conf->{$opt} = $value;
1038 } elsif ($opt eq 'cpuunits') {
1039 $conf->{$opt} = $value;
1040 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1041 } elsif ($opt eq 'description') {
1042 $conf->{$opt} = $value;
1043 } elsif ($opt =~ m/^net(\d+)$/) {
1045 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1047 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1049 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1051 } elsif ($opt eq 'protection') {
1052 $conf->{$opt} = $value ?
1 : 0;
1053 } elsif ($opt =~ m/^mp(\d+)$/) {
1054 next if $hotplug_error->($opt);
1055 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
1056 my $old = $conf->{$opt};
1057 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1058 if ($mp->{type
} eq 'volume') {
1059 &$check_content_type($mp);
1060 $used_volids->{$mp->{volume
}} = 1;
1061 &$rescan_volume($mp);
1062 $conf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp);
1064 $conf->{$opt} = $value;
1066 if (defined($old)) {
1067 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
1068 if ($mp->{type
} eq 'volume') {
1069 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1073 } elsif ($opt eq 'rootfs') {
1074 next if $hotplug_error->($opt);
1075 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
1076 my $old = $conf->{$opt};
1077 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
1078 if ($mp->{type
} eq 'volume') {
1079 &$check_content_type($mp);
1080 $used_volids->{$mp->{volume
}} = 1;
1081 &$rescan_volume($mp);
1082 $conf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, 1);
1084 $conf->{$opt} = $value;
1086 if (defined($old)) {
1087 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
1088 if ($mp->{type
} eq 'volume') {
1089 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1093 } elsif ($opt eq 'unprivileged') {
1094 die "unable to modify read-only option: '$opt'\n";
1095 } elsif ($opt eq 'ostype') {
1096 next if $hotplug_error->($opt);
1097 $conf->{$opt} = $value;
1098 } elsif ($opt eq 'features') {
1099 next if $hotplug_error->($opt);
1100 $conf->{$opt} = $value;
1101 } elsif ($opt eq 'hookscript') {
1102 PVE
::GuestHelpers
::check_hookscript
($value);
1103 $conf->{$opt} = $value;
1105 die "implement me: $opt";
1108 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1111 # Apply deletions and creations of new volumes
1112 if (@deleted_volumes) {
1113 my $storage_cfg = PVE
::Storage
::config
();
1114 foreach my $volume (@deleted_volumes) {
1115 next if $used_volids->{$volume}; # could have been re-added, too
1116 # also check for references in snapshots
1117 next if $class->is_volume_in_use($conf, $volume, 1);
1118 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1123 my $storage_cfg = PVE
::Storage
::config
();
1124 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
1127 # This should be the last thing we do here
1128 if ($running && scalar(@nohotplug)) {
1129 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1134 my ($class, $key, $value) = @_;
1136 die "unknown setting '$key'\n" if !$confdesc->{$key};
1138 my $type = $confdesc->{$key}->{type
};
1140 if (!defined($value)) {
1141 die "got undefined value\n";
1144 if ($value =~ m/[\n\r]/) {
1145 die "property contains a line feed\n";
1148 if ($type eq 'boolean') {
1149 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1150 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1151 die "type check ('boolean') failed - got '$value'\n";
1152 } elsif ($type eq 'integer') {
1153 return int($1) if $value =~ m/^(\d+)$/;
1154 die "type check ('integer') failed - got '$value'\n";
1155 } elsif ($type eq 'number') {
1156 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1157 die "type check ('number') failed - got '$value'\n";
1158 } elsif ($type eq 'string') {
1159 if (my $fmt = $confdesc->{$key}->{format
}) {
1160 PVE
::JSONSchema
::check_format
($fmt, $value);
1165 die "internal error"
1170 # add JSON properties for create and set function
1171 sub json_config_properties
{
1172 my ($class, $prop) = @_;
1174 foreach my $opt (keys %$confdesc) {
1175 next if $opt eq 'parent' || $opt eq 'snaptime';
1176 next if $prop->{$opt};
1177 $prop->{$opt} = $confdesc->{$opt};
1183 sub __parse_ct_mountpoint_full
{
1184 my ($class, $desc, $data, $noerr) = @_;
1189 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1191 return undef if $noerr;
1195 if (defined(my $size = $res->{size
})) {
1196 $size = PVE
::JSONSchema
::parse_size
($size);
1197 if (!defined($size)) {
1198 return undef if $noerr;
1199 die "invalid size: $size\n";
1201 $res->{size
} = $size;
1204 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1209 sub parse_ct_rootfs
{
1210 my ($class, $data, $noerr) = @_;
1212 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1214 $res->{mp
} = '/' if defined($res);
1219 sub parse_ct_mountpoint
{
1220 my ($class, $data, $noerr) = @_;
1222 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1225 sub print_ct_mountpoint
{
1226 my ($class, $info, $nomp) = @_;
1227 my $skip = [ 'type' ];
1228 push @$skip, 'mp' if $nomp;
1229 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1232 sub print_lxc_network
{
1233 my ($class, $net) = @_;
1234 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1237 sub parse_lxc_network
{
1238 my ($class, $data) = @_;
1242 return $res if !$data;
1244 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1246 $res->{type
} = 'veth';
1247 if (!$res->{hwaddr
}) {
1248 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1249 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1255 sub parse_features
{
1256 my ($class, $data) = @_;
1257 return {} if !$data;
1258 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1262 my ($class, $name) = @_;
1264 return defined($confdesc->{$name});
1266 # END JSON config code
1268 sub classify_mountpoint
{
1269 my ($class, $vol) = @_;
1270 if ($vol =~ m!^/!) {
1271 return 'device' if $vol =~ m!^/dev/!;
1277 my $is_volume_in_use = sub {
1278 my ($class, $config, $volid) = @_;
1281 $class->foreach_mountpoint($config, sub {
1282 my ($ms, $mountpoint) = @_;
1284 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1290 sub is_volume_in_use_by_snapshots
{
1291 my ($class, $config, $volid) = @_;
1293 if (my $snapshots = $config->{snapshots
}) {
1294 foreach my $snap (keys %$snapshots) {
1295 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1302 sub is_volume_in_use
{
1303 my ($class, $config, $volid, $include_snapshots) = @_;
1304 return 1 if $is_volume_in_use->($class, $config, $volid);
1305 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1309 sub has_dev_console
{
1310 my ($class, $conf) = @_;
1312 return !(defined($conf->{console
}) && !$conf->{console
});
1316 my ($class, $conf, $keyname) = @_;
1318 if (my $lxcconf = $conf->{lxc
}) {
1319 foreach my $entry (@$lxcconf) {
1320 my ($key, undef) = @$entry;
1321 return 1 if $key eq $keyname;
1329 my ($class, $conf) = @_;
1331 return $conf->{tty
} // $confdesc->{tty
}->{default};
1335 my ($class, $conf) = @_;
1337 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1340 sub mountpoint_names
{
1341 my ($class, $reverse) = @_;
1343 my @names = ('rootfs');
1345 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1346 push @names, "mp$i";
1349 return $reverse ?
reverse @names : @names;
1352 sub foreach_mountpoint_full
{
1353 my ($class, $conf, $reverse, $func, @param) = @_;
1355 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1356 foreach my $key (@$mps) {
1357 my $value = $conf->{$key};
1358 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1359 next if !defined($mountpoint);
1361 &$func($key, $mountpoint, @param);
1365 sub foreach_mountpoint
{
1366 my ($class, $conf, $func, @param) = @_;
1368 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1371 sub foreach_mountpoint_reverse
{
1372 my ($class, $conf, $func, @param) = @_;
1374 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1377 sub get_vm_volumes
{
1378 my ($class, $conf, $excludes) = @_;
1382 $class->foreach_mountpoint($conf, sub {
1383 my ($ms, $mountpoint) = @_;
1385 return if $excludes && $ms eq $excludes;
1387 my $volid = $mountpoint->{volume
};
1388 return if !$volid || $mountpoint->{type
} ne 'volume';
1390 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1393 push @$vollist, $volid;
1399 sub get_replicatable_volumes
{
1400 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1404 my $test_volid = sub {
1405 my ($volid, $mountpoint) = @_;
1409 my $mptype = $mountpoint->{type
};
1410 my $replicate = $mountpoint->{replicate
} // 1;
1412 if ($mptype ne 'volume') {
1413 # skip bindmounts if replicate = 0 even for cleanup,
1414 # since bind mounts could not have been replicated ever
1415 return if !$replicate;
1416 die "unable to replicate mountpoint type '$mptype'\n";
1419 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1420 return if !$storeid;
1422 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1423 return if $scfg->{shared
};
1425 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1426 return if !$owner || ($owner != $vmid);
1428 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1430 return if !$cleanup && !$replicate;
1432 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1433 return if $cleanup || $noerr;
1434 die "missing replicate feature on volume '$volid'\n";
1437 $volhash->{$volid} = 1;
1440 $class->foreach_mountpoint($conf, sub {
1441 my ($ms, $mountpoint) = @_;
1442 $test_volid->($mountpoint->{volume
}, $mountpoint);
1445 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1446 my $snap = $conf->{snapshots
}->{$snapname};
1447 $class->foreach_mountpoint($snap, sub {
1448 my ($ms, $mountpoint) = @_;
1449 $test_volid->($mountpoint->{volume
}, $mountpoint);
1453 # add 'unusedX' volumes to volhash
1454 foreach my $key (keys %$conf) {
1455 if ($key =~ m/^unused/) {
1456 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });