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',
279 description
=> "Lock/unlock the VM.",
280 enum
=> [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
285 description
=> "Specifies whether a VM will be started during system bootup.",
288 startup
=> get_standard_option
('pve-startup-order'),
292 description
=> "Enable/disable Template.",
298 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
299 description
=> "OS architecture type.",
305 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
306 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.",
311 description
=> "Attach a console device (/dev/console) to the container.",
317 description
=> "Specify the number of tty available to the container",
325 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
332 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.",
340 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.",
348 description
=> "Amount of RAM for the VM in MB.",
355 description
=> "Amount of SWAP for the VM in MB.",
361 description
=> "Set a host name for the container.",
362 type
=> 'string', format
=> 'dns-name',
368 description
=> "Container description. Only used on the configuration web interface.",
372 type
=> 'string', format
=> 'dns-name-list',
373 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
377 type
=> 'string', format
=> 'address-list',
378 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.",
380 rootfs
=> get_standard_option
('pve-ct-rootfs'),
383 type
=> 'string', format
=> 'pve-configid',
385 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
389 description
=> "Timestamp for snapshots.",
395 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).",
397 enum
=> ['shell', 'console', 'tty'],
403 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
409 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
414 my $valid_lxc_conf_keys = {
415 'lxc.apparmor.profile' => 1,
416 'lxc.apparmor.allow_incomplete' => 1,
417 'lxc.apparmor.allow_nesting' => 1,
418 'lxc.apparmor.raw' => 1,
419 'lxc.selinux.context' => 1,
423 'lxc.signal.halt' => 1,
424 'lxc.signal.reboot' => 1,
425 'lxc.signal.stop' => 1,
428 'lxc.console.logfile' => 1,
429 'lxc.console.path' => 1,
431 'lxc.devtty.dir' => 1,
432 'lxc.hook.autodev' => 1,
435 'lxc.mount.fstab' => 1,
436 'lxc.mount.entry' => 1,
437 'lxc.mount.auto' => 1,
438 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
439 'lxc.rootfs.mount' => 1,
440 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
441 ', please use mount point options in the "rootfs" key',
446 'lxc.seccomp.profile' => 1,
448 'lxc.hook.pre-start' => 1,
449 'lxc.hook.pre-mount' => 1,
450 'lxc.hook.mount' => 1,
451 'lxc.hook.start' => 1,
452 'lxc.hook.stop' => 1,
453 'lxc.hook.post-stop' => 1,
454 'lxc.hook.clone' => 1,
455 'lxc.hook.destroy' => 1,
456 'lxc.log.level' => 1,
458 'lxc.start.auto' => 1,
459 'lxc.start.delay' => 1,
460 'lxc.start.order' => 1,
462 'lxc.environment' => 1,
465 my $deprecated_lxc_conf_keys = {
466 # Deprecated (removed with lxc 3.0):
467 'lxc.aa_profile' => 'lxc.apparmor.profile',
468 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
469 'lxc.console' => 'lxc.console.path',
470 'lxc.devttydir' => 'lxc.tty.dir',
471 'lxc.haltsignal' => 'lxc.signal.halt',
472 'lxc.rebootsignal' => 'lxc.signal.reboot',
473 'lxc.stopsignal' => 'lxc.signal.stop',
474 'lxc.id_map' => 'lxc.idmap',
475 'lxc.init_cmd' => 'lxc.init.cmd',
476 'lxc.loglevel' => 'lxc.log.level',
477 'lxc.logfile' => 'lxc.log.file',
478 'lxc.mount' => 'lxc.mount.fstab',
479 'lxc.network.type' => 'lxc.net.INDEX.type',
480 'lxc.network.flags' => 'lxc.net.INDEX.flags',
481 'lxc.network.link' => 'lxc.net.INDEX.link',
482 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
483 'lxc.network.name' => 'lxc.net.INDEX.name',
484 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
485 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
486 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
487 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
488 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
489 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
490 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
491 'lxc.pts' => 'lxc.pty.max',
492 'lxc.se_context' => 'lxc.selinux.context',
493 'lxc.seccomp' => 'lxc.seccomp.profile',
494 'lxc.tty' => 'lxc.tty.max',
495 'lxc.utsname' => 'lxc.uts.name',
498 sub is_valid_lxc_conf_key
{
499 my ($vmid, $key) = @_;
500 if ($key =~ /^lxc\.limit\./) {
501 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
504 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
505 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
508 my $validity = $valid_lxc_conf_keys->{$key};
509 return $validity if defined($validity);
510 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
511 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
512 || $key =~ /^lxc\.net\./; # allow custom network definitions
516 our $netconf_desc = {
520 description
=> "Network interface type.",
525 format_description
=> 'string',
526 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
527 pattern
=> '[-_.\w\d]+',
531 format_description
=> 'bridge',
532 description
=> 'Bridge to attach the network device to.',
533 pattern
=> '[-_.\w\d]+',
538 format_description
=> "XX:XX:XX:XX:XX:XX",
539 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)',
540 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
545 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
546 minimum
=> 64, # minimum ethernet frame is 64 bytes
551 format
=> 'pve-ipv4-config',
552 format_description
=> '(IPv4/CIDR|dhcp|manual)',
553 description
=> 'IPv4 address in CIDR format.',
559 format_description
=> 'GatewayIPv4',
560 description
=> 'Default gateway for IPv4 traffic.',
565 format
=> 'pve-ipv6-config',
566 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
567 description
=> 'IPv6 address in CIDR format.',
573 format_description
=> 'GatewayIPv6',
574 description
=> 'Default gateway for IPv6 traffic.',
579 description
=> "Controls whether this interface's firewall rules should be used.",
586 description
=> "VLAN tag for this interface.",
591 pattern
=> qr/\d+(?:;\d+)*/,
592 format_description
=> 'vlanid[;vlanid...]',
593 description
=> "VLAN ids to pass through the interface",
598 format_description
=> 'mbps',
599 description
=> "Apply rate limiting to the interface",
603 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
605 my $MAX_LXC_NETWORKS = 10;
606 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
607 $confdesc->{"net$i"} = {
609 type
=> 'string', format
=> $netconf_desc,
610 description
=> "Specifies network interfaces for the container.",
614 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
615 sub verify_lxc_mp_string
{
616 my ($mp, $noerr) = @_;
620 # /. or /.. at the end
621 # ../ at the beginning
623 if($mp =~ m
@/\.\
.?
/@ ||
626 return undef if $noerr;
627 die "$mp contains illegal character sequences\n";
636 description
=> 'Whether to include the mount point in backups.',
637 verbose_description
=> 'Whether to include the mount point in backups '.
638 '(only used for volume mount points).',
643 format
=> 'pve-lxc-mp-string',
644 format_description
=> 'Path',
645 description
=> 'Path to the mount point as seen from inside the container '.
646 '(must not contain symlinks).',
647 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
648 "NOTE: Must not contain any symlinks for security reasons."
651 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
655 type
=> 'string', format
=> 'pve-volume-id',
656 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
659 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
660 $confdesc->{"mp$i"} = {
662 type
=> 'string', format
=> $mp_desc,
663 description
=> "Use volume as container mount point.",
668 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
669 $confdesc->{"unused$i"} = $unuseddesc;
672 sub parse_pct_config
{
673 my ($filename, $raw) = @_;
675 return undef if !defined($raw);
678 digest
=> Digest
::SHA
::sha1_hex
($raw),
682 $filename =~ m
|/lxc/(\d
+).conf
$|
683 || die "got strange filename '$filename'";
691 my @lines = split(/\n/, $raw);
692 foreach my $line (@lines) {
693 next if $line =~ m/^\s*$/;
695 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
697 $conf->{description
} = $descr if $descr;
699 $conf = $res->{snapshots
}->{$section} = {};
703 if ($line =~ m/^\#(.*)\s*$/) {
704 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
708 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
711 my $validity = is_valid_lxc_conf_key
($vmid, $key);
712 if ($validity eq 1) {
713 push @{$conf->{lxc
}}, [$key, $value];
714 } elsif (my $errmsg = $validity) {
715 warn "vm $vmid - $key: $errmsg\n";
717 warn "vm $vmid - unable to parse config: $line\n";
719 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
720 $descr .= PVE
::Tools
::decode_text
($2);
721 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
722 $conf->{snapstate
} = $1;
723 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
726 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
727 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
728 $conf->{$key} = $value;
730 warn "vm $vmid - unable to parse config: $line\n";
734 $conf->{description
} = $descr if $descr;
736 delete $res->{snapstate
}; # just to be sure
741 sub write_pct_config
{
742 my ($filename, $conf) = @_;
744 delete $conf->{snapstate
}; # just to be sure
746 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
747 my $used_volids = {};
748 foreach my $vid (@$volidlist) {
749 $used_volids->{$vid} = 1;
752 # remove 'unusedX' settings if the volume is still used
753 foreach my $key (keys %$conf) {
754 my $value = $conf->{$key};
755 if ($key =~ m/^unused/ && $used_volids->{$value}) {
756 delete $conf->{$key};
760 my $generate_raw_config = sub {
765 # add description as comment to top of file
766 my $descr = $conf->{description
} || '';
767 foreach my $cl (split(/\n/, $descr)) {
768 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
771 foreach my $key (sort keys %$conf) {
772 next if $key eq 'digest' || $key eq 'description' ||
773 $key eq 'pending' || $key eq 'snapshots' ||
774 $key eq 'snapname' || $key eq 'lxc';
775 my $value = $conf->{$key};
776 die "detected invalid newline inside property '$key'\n"
778 $raw .= "$key: $value\n";
781 if (my $lxcconf = $conf->{lxc
}) {
782 foreach my $entry (@$lxcconf) {
783 my ($k, $v) = @$entry;
791 my $raw = &$generate_raw_config($conf);
793 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
794 $raw .= "\n[$snapname]\n";
795 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
801 sub update_pct_config
{
802 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
811 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
812 $rootdir = "/proc/$pid/root";
815 my $hotplug_error = sub {
824 if (defined($delete)) {
825 foreach my $opt (@$delete) {
826 if (!exists($conf->{$opt})) {
831 if ($opt eq 'memory' || $opt eq 'rootfs') {
832 die "unable to delete required option '$opt'\n";
833 } elsif ($opt eq 'hostname') {
834 delete $conf->{$opt};
835 } elsif ($opt eq 'swap') {
836 delete $conf->{$opt};
837 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
838 "memory.memsw.limit_in_bytes", -1);
839 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
840 delete $conf->{$opt};
841 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
842 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
843 next if $hotplug_error->($opt);
844 delete $conf->{$opt};
845 } elsif ($opt eq 'cores') {
846 delete $conf->{$opt}; # rest is handled by pvestatd
847 } elsif ($opt eq 'cpulimit') {
848 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
849 delete $conf->{$opt};
850 } elsif ($opt eq 'cpuunits') {
851 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
852 delete $conf->{$opt};
853 } elsif ($opt =~ m/^net(\d)$/) {
854 delete $conf->{$opt};
857 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
858 } elsif ($opt eq 'protection') {
859 delete $conf->{$opt};
860 } elsif ($opt =~ m/^unused(\d+)$/) {
861 next if $hotplug_error->($opt);
862 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
863 push @deleted_volumes, $conf->{$opt};
864 delete $conf->{$opt};
865 } elsif ($opt =~ m/^mp(\d+)$/) {
866 next if $hotplug_error->($opt);
867 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
868 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
869 delete $conf->{$opt};
870 if ($mp->{type
} eq 'volume') {
871 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
873 } elsif ($opt eq 'unprivileged') {
874 die "unable to delete read-only option: '$opt'\n";
876 die "implement me (delete: $opt)"
878 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
882 # There's no separate swap size to configure, there's memory and "total"
883 # memory (iow. memory+swap). This means we have to change them together.
884 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
885 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
886 if (defined($wanted_memory) || defined($wanted_swap)) {
888 my $old_memory = ($conf->{memory
} || 512);
889 my $old_swap = ($conf->{swap
} || 0);
891 $wanted_memory //= $old_memory;
892 $wanted_swap //= $old_swap;
894 my $total = $wanted_memory + $wanted_swap;
896 my $old_total = $old_memory + $old_swap;
897 if ($total > $old_total) {
898 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
899 "memory.memsw.limit_in_bytes",
900 int($total*1024*1024));
901 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
902 "memory.limit_in_bytes",
903 int($wanted_memory*1024*1024));
905 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
906 "memory.limit_in_bytes",
907 int($wanted_memory*1024*1024));
908 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
909 "memory.memsw.limit_in_bytes",
910 int($total*1024*1024));
913 $conf->{memory
} = $wanted_memory;
914 $conf->{swap
} = $wanted_swap;
916 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
919 my $storecfg = PVE
::Storage
::config
();
921 my $used_volids = {};
922 my $check_content_type = sub {
924 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
925 my $storage_config = PVE
::Storage
::storage_config
($storecfg, $sid);
926 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
927 if !$storage_config->{content
}->{rootdir
};
930 my $rescan_volume = sub {
933 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5)
934 if !defined($mp->{size
});
936 warn "Could not rescan volume size - $@\n" if $@;
939 foreach my $opt (keys %$param) {
940 my $value = $param->{$opt};
941 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
942 if ($opt eq 'hostname' || $opt eq 'arch') {
943 $conf->{$opt} = $value;
944 } elsif ($opt eq 'onboot') {
945 $conf->{$opt} = $value ?
1 : 0;
946 } elsif ($opt eq 'startup') {
947 $conf->{$opt} = $value;
948 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
949 next if $hotplug_error->($opt);
950 $conf->{$opt} = $value;
951 } elsif ($opt eq 'nameserver') {
952 next if $hotplug_error->($opt);
953 my $list = PVE
::LXC
::verify_nameserver_list
($value);
954 $conf->{$opt} = $list;
955 } elsif ($opt eq 'searchdomain') {
956 next if $hotplug_error->($opt);
957 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
958 $conf->{$opt} = $list;
959 } elsif ($opt eq 'cores') {
960 $conf->{$opt} = $value;# rest is handled by pvestatd
961 } elsif ($opt eq 'cpulimit') {
963 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
965 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
967 $conf->{$opt} = $value;
968 } elsif ($opt eq 'cpuunits') {
969 $conf->{$opt} = $value;
970 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
971 } elsif ($opt eq 'description') {
972 $conf->{$opt} = $value;
973 } elsif ($opt =~ m/^net(\d+)$/) {
975 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
977 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
979 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
981 } elsif ($opt eq 'protection') {
982 $conf->{$opt} = $value ?
1 : 0;
983 } elsif ($opt =~ m/^mp(\d+)$/) {
984 next if $hotplug_error->($opt);
985 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
986 my $old = $conf->{$opt};
987 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
988 if ($mp->{type
} eq 'volume') {
989 &$check_content_type($mp);
990 $used_volids->{$mp->{volume
}} = 1;
991 &$rescan_volume($mp);
992 $conf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp);
994 $conf->{$opt} = $value;
997 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
998 if ($mp->{type
} eq 'volume') {
999 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1003 } elsif ($opt eq 'rootfs') {
1004 next if $hotplug_error->($opt);
1005 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
1006 my $old = $conf->{$opt};
1007 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
1008 if ($mp->{type
} eq 'volume') {
1009 &$check_content_type($mp);
1010 $used_volids->{$mp->{volume
}} = 1;
1011 &$rescan_volume($mp);
1012 $conf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, 1);
1014 $conf->{$opt} = $value;
1016 if (defined($old)) {
1017 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
1018 if ($mp->{type
} eq 'volume') {
1019 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
1023 } elsif ($opt eq 'unprivileged') {
1024 die "unable to modify read-only option: '$opt'\n";
1025 } elsif ($opt eq 'ostype') {
1026 next if $hotplug_error->($opt);
1027 $conf->{$opt} = $value;
1029 die "implement me: $opt";
1032 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
1035 # Apply deletions and creations of new volumes
1036 if (@deleted_volumes) {
1037 my $storage_cfg = PVE
::Storage
::config
();
1038 foreach my $volume (@deleted_volumes) {
1039 next if $used_volids->{$volume}; # could have been re-added, too
1040 # also check for references in snapshots
1041 next if $class->is_volume_in_use($conf, $volume, 1);
1042 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1047 my $storage_cfg = PVE
::Storage
::config
();
1048 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
1051 # This should be the last thing we do here
1052 if ($running && scalar(@nohotplug)) {
1053 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1058 my ($class, $key, $value) = @_;
1060 die "unknown setting '$key'\n" if !$confdesc->{$key};
1062 my $type = $confdesc->{$key}->{type
};
1064 if (!defined($value)) {
1065 die "got undefined value\n";
1068 if ($value =~ m/[\n\r]/) {
1069 die "property contains a line feed\n";
1072 if ($type eq 'boolean') {
1073 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1074 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1075 die "type check ('boolean') failed - got '$value'\n";
1076 } elsif ($type eq 'integer') {
1077 return int($1) if $value =~ m/^(\d+)$/;
1078 die "type check ('integer') failed - got '$value'\n";
1079 } elsif ($type eq 'number') {
1080 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1081 die "type check ('number') failed - got '$value'\n";
1082 } elsif ($type eq 'string') {
1083 if (my $fmt = $confdesc->{$key}->{format
}) {
1084 PVE
::JSONSchema
::check_format
($fmt, $value);
1089 die "internal error"
1094 # add JSON properties for create and set function
1095 sub json_config_properties
{
1096 my ($class, $prop) = @_;
1098 foreach my $opt (keys %$confdesc) {
1099 next if $opt eq 'parent' || $opt eq 'snaptime';
1100 next if $prop->{$opt};
1101 $prop->{$opt} = $confdesc->{$opt};
1107 sub __parse_ct_mountpoint_full
{
1108 my ($class, $desc, $data, $noerr) = @_;
1113 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1115 return undef if $noerr;
1119 if (defined(my $size = $res->{size
})) {
1120 $size = PVE
::JSONSchema
::parse_size
($size);
1121 if (!defined($size)) {
1122 return undef if $noerr;
1123 die "invalid size: $size\n";
1125 $res->{size
} = $size;
1128 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1133 sub parse_ct_rootfs
{
1134 my ($class, $data, $noerr) = @_;
1136 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1138 $res->{mp
} = '/' if defined($res);
1143 sub parse_ct_mountpoint
{
1144 my ($class, $data, $noerr) = @_;
1146 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1149 sub print_ct_mountpoint
{
1150 my ($class, $info, $nomp) = @_;
1151 my $skip = [ 'type' ];
1152 push @$skip, 'mp' if $nomp;
1153 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1156 sub print_lxc_network
{
1157 my ($class, $net) = @_;
1158 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1161 sub parse_lxc_network
{
1162 my ($class, $data) = @_;
1166 return $res if !$data;
1168 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1170 $res->{type
} = 'veth';
1171 if (!$res->{hwaddr
}) {
1172 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1173 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1180 my ($class, $name) = @_;
1182 return defined($confdesc->{$name});
1184 # END JSON config code
1186 sub classify_mountpoint
{
1187 my ($class, $vol) = @_;
1188 if ($vol =~ m!^/!) {
1189 return 'device' if $vol =~ m!^/dev/!;
1195 my $is_volume_in_use = sub {
1196 my ($class, $config, $volid) = @_;
1199 $class->foreach_mountpoint($config, sub {
1200 my ($ms, $mountpoint) = @_;
1202 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1208 sub is_volume_in_use_by_snapshots
{
1209 my ($class, $config, $volid) = @_;
1211 if (my $snapshots = $config->{snapshots
}) {
1212 foreach my $snap (keys %$snapshots) {
1213 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1220 sub is_volume_in_use
{
1221 my ($class, $config, $volid, $include_snapshots) = @_;
1222 return 1 if $is_volume_in_use->($class, $config, $volid);
1223 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1227 sub has_dev_console
{
1228 my ($class, $conf) = @_;
1230 return !(defined($conf->{console
}) && !$conf->{console
});
1234 my ($class, $conf, $keyname) = @_;
1236 if (my $lxcconf = $conf->{lxc
}) {
1237 foreach my $entry (@$lxcconf) {
1238 my ($key, undef) = @$entry;
1239 return 1 if $key eq $keyname;
1247 my ($class, $conf) = @_;
1249 return $conf->{tty
} // $confdesc->{tty
}->{default};
1253 my ($class, $conf) = @_;
1255 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1258 sub mountpoint_names
{
1259 my ($class, $reverse) = @_;
1261 my @names = ('rootfs');
1263 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1264 push @names, "mp$i";
1267 return $reverse ?
reverse @names : @names;
1270 sub foreach_mountpoint_full
{
1271 my ($class, $conf, $reverse, $func, @param) = @_;
1273 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1274 foreach my $key (@$mps) {
1275 my $value = $conf->{$key};
1276 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1277 next if !defined($mountpoint);
1279 &$func($key, $mountpoint, @param);
1283 sub foreach_mountpoint
{
1284 my ($class, $conf, $func, @param) = @_;
1286 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1289 sub foreach_mountpoint_reverse
{
1290 my ($class, $conf, $func, @param) = @_;
1292 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1295 sub get_vm_volumes
{
1296 my ($class, $conf, $excludes) = @_;
1300 $class->foreach_mountpoint($conf, sub {
1301 my ($ms, $mountpoint) = @_;
1303 return if $excludes && $ms eq $excludes;
1305 my $volid = $mountpoint->{volume
};
1306 return if !$volid || $mountpoint->{type
} ne 'volume';
1308 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1311 push @$vollist, $volid;
1317 sub get_replicatable_volumes
{
1318 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1322 my $test_volid = sub {
1323 my ($volid, $mountpoint) = @_;
1327 my $mptype = $mountpoint->{type
};
1328 my $replicate = $mountpoint->{replicate
} // 1;
1330 if ($mptype ne 'volume') {
1331 # skip bindmounts if replicate = 0 even for cleanup,
1332 # since bind mounts could not have been replicated ever
1333 return if !$replicate;
1334 die "unable to replicate mountpoint type '$mptype'\n";
1337 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1338 return if !$storeid;
1340 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1341 return if $scfg->{shared
};
1343 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1344 return if !$owner || ($owner != $vmid);
1346 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1348 return if !$cleanup && !$replicate;
1350 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1351 return if $cleanup || $noerr;
1352 die "missing replicate feature on volume '$volid'\n";
1355 $volhash->{$volid} = 1;
1358 $class->foreach_mountpoint($conf, sub {
1359 my ($ms, $mountpoint) = @_;
1360 $test_volid->($mountpoint->{volume
}, $mountpoint);
1363 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1364 my $snap = $conf->{snapshots
}->{$snapname};
1365 $class->foreach_mountpoint($snap, sub {
1366 my ($ms, $mountpoint) = @_;
1367 $test_volid->($mountpoint->{volume
}, $mountpoint);
1371 # add 'unusedX' volumes to volhash
1372 foreach my $key (keys %$conf) {
1373 if ($key =~ m/^unused/) {
1374 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });