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 = 10;
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
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
168 if $class->__snapshot_check_running($vmid);
171 sub __snapshot_rollback_vm_start
{
172 my ($class, $vmid, $vmstate, $forcemachine);
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(migrate backup snapshot rollback)],
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'],
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.",
393 replicate
=> get_standard_option
('pve-replicate'),
396 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).",
398 enum
=> ['shell', 'console', 'tty'],
404 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
410 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
415 my $valid_lxc_conf_keys = {
419 'lxc.haltsignal' => 1,
420 'lxc.rebootsignal' => 1,
421 'lxc.stopsignal' => 1,
423 'lxc.network.type' => 1,
424 'lxc.network.flags' => 1,
425 'lxc.network.link' => 1,
426 'lxc.network.mtu' => 1,
427 'lxc.network.name' => 1,
428 'lxc.network.hwaddr' => 1,
429 'lxc.network.ipv4' => 1,
430 'lxc.network.ipv4.gateway' => 1,
431 'lxc.network.ipv6' => 1,
432 'lxc.network.ipv6.gateway' => 1,
433 'lxc.network.script.up' => 1,
434 'lxc.network.script.down' => 1,
436 'lxc.console.logfile' => 1,
439 'lxc.devttydir' => 1,
440 'lxc.hook.autodev' => 1,
444 'lxc.mount.entry' => 1,
445 'lxc.mount.auto' => 1,
446 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
447 'lxc.rootfs.mount' => 1,
448 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
449 ', please use mount point options in the "rootfs" key',
454 'lxc.aa_profile' => 1,
455 'lxc.aa_allow_incomplete' => 1,
456 'lxc.se_context' => 1,
459 'lxc.hook.pre-start' => 1,
460 'lxc.hook.pre-mount' => 1,
461 'lxc.hook.mount' => 1,
462 'lxc.hook.start' => 1,
463 'lxc.hook.stop' => 1,
464 'lxc.hook.post-stop' => 1,
465 'lxc.hook.clone' => 1,
466 'lxc.hook.destroy' => 1,
469 'lxc.start.auto' => 1,
470 'lxc.start.delay' => 1,
471 'lxc.start.order' => 1,
473 'lxc.environment' => 1,
476 our $netconf_desc = {
480 description
=> "Network interface type.",
485 format_description
=> 'string',
486 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
487 pattern
=> '[-_.\w\d]+',
491 format_description
=> 'bridge',
492 description
=> 'Bridge to attach the network device to.',
493 pattern
=> '[-_.\w\d]+',
498 format_description
=> "XX:XX:XX:XX:XX:XX",
499 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)',
500 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
505 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
506 minimum
=> 64, # minimum ethernet frame is 64 bytes
511 format
=> 'pve-ipv4-config',
512 format_description
=> 'IPv4Format/CIDR',
513 description
=> 'IPv4 address in CIDR format.',
519 format_description
=> 'GatewayIPv4',
520 description
=> 'Default gateway for IPv4 traffic.',
525 format
=> 'pve-ipv6-config',
526 format_description
=> 'IPv6Format/CIDR',
527 description
=> 'IPv6 address in CIDR format.',
533 format_description
=> 'GatewayIPv6',
534 description
=> 'Default gateway for IPv6 traffic.',
539 description
=> "Controls whether this interface's firewall rules should be used.",
546 description
=> "VLAN tag for this interface.",
551 pattern
=> qr/\d+(?:;\d+)*/,
552 format_description
=> 'vlanid[;vlanid...]',
553 description
=> "VLAN ids to pass through the interface",
558 format_description
=> 'mbps',
559 description
=> "Apply rate limiting to the interface",
563 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
565 my $MAX_LXC_NETWORKS = 10;
566 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
567 $confdesc->{"net$i"} = {
569 type
=> 'string', format
=> $netconf_desc,
570 description
=> "Specifies network interfaces for the container.",
574 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
575 sub verify_lxc_mp_string
{
576 my ($mp, $noerr) = @_;
580 # /. or /.. at the end
581 # ../ at the beginning
583 if($mp =~ m
@/\.\
.?
/@ ||
586 return undef if $noerr;
587 die "$mp contains illegal character sequences\n";
596 description
=> 'Whether to include the mount point in backups.',
597 verbose_description
=> 'Whether to include the mount point in backups '.
598 '(only used for volume mount points).',
603 format
=> 'pve-lxc-mp-string',
604 format_description
=> 'Path',
605 description
=> 'Path to the mount point as seen from inside the container '.
606 '(must not contain symlinks).',
607 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
608 "NOTE: Must not contain any symlinks for security reasons."
611 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
615 type
=> 'string', format
=> 'pve-volume-id',
616 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
619 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
620 $confdesc->{"mp$i"} = {
622 type
=> 'string', format
=> $mp_desc,
623 description
=> "Use volume as container mount point.",
628 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
629 $confdesc->{"unused$i"} = $unuseddesc;
632 sub parse_pct_config
{
633 my ($filename, $raw) = @_;
635 return undef if !defined($raw);
638 digest
=> Digest
::SHA
::sha1_hex
($raw),
642 $filename =~ m
|/lxc/(\d
+).conf
$|
643 || die "got strange filename '$filename'";
651 my @lines = split(/\n/, $raw);
652 foreach my $line (@lines) {
653 next if $line =~ m/^\s*$/;
655 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
657 $conf->{description
} = $descr if $descr;
659 $conf = $res->{snapshots
}->{$section} = {};
663 if ($line =~ m/^\#(.*)\s*$/) {
664 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
668 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
671 my $validity = $valid_lxc_conf_keys->{$key} || 0;
672 if ($validity eq 1 || $key =~ m/^lxc\.(?:cgroup|limit)\./) {
673 push @{$conf->{lxc
}}, [$key, $value];
674 } elsif (my $errmsg = $validity) {
675 warn "vm $vmid - $key: $errmsg\n";
677 warn "vm $vmid - unable to parse config: $line\n";
679 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
680 $descr .= PVE
::Tools
::decode_text
($2);
681 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
682 $conf->{snapstate
} = $1;
683 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
686 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
687 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
688 $conf->{$key} = $value;
690 warn "vm $vmid - unable to parse config: $line\n";
694 $conf->{description
} = $descr if $descr;
696 delete $res->{snapstate
}; # just to be sure
701 sub write_pct_config
{
702 my ($filename, $conf) = @_;
704 delete $conf->{snapstate
}; # just to be sure
706 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
707 my $used_volids = {};
708 foreach my $vid (@$volidlist) {
709 $used_volids->{$vid} = 1;
712 # remove 'unusedX' settings if the volume is still used
713 foreach my $key (keys %$conf) {
714 my $value = $conf->{$key};
715 if ($key =~ m/^unused/ && $used_volids->{$value}) {
716 delete $conf->{$key};
720 my $generate_raw_config = sub {
725 # add description as comment to top of file
726 my $descr = $conf->{description
} || '';
727 foreach my $cl (split(/\n/, $descr)) {
728 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
731 foreach my $key (sort keys %$conf) {
732 next if $key eq 'digest' || $key eq 'description' ||
733 $key eq 'pending' || $key eq 'snapshots' ||
734 $key eq 'snapname' || $key eq 'lxc';
735 my $value = $conf->{$key};
736 die "detected invalid newline inside property '$key'\n"
738 $raw .= "$key: $value\n";
741 if (my $lxcconf = $conf->{lxc
}) {
742 foreach my $entry (@$lxcconf) {
743 my ($k, $v) = @$entry;
751 my $raw = &$generate_raw_config($conf);
753 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
754 $raw .= "\n[$snapname]\n";
755 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
761 sub update_pct_config
{
762 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
771 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
772 $rootdir = "/proc/$pid/root";
775 my $hotplug_error = sub {
784 if (defined($delete)) {
785 foreach my $opt (@$delete) {
786 if (!exists($conf->{$opt})) {
791 if ($opt eq 'memory' || $opt eq 'rootfs') {
792 die "unable to delete required option '$opt'\n";
793 } elsif ($opt eq 'hostname') {
794 delete $conf->{$opt};
795 } elsif ($opt eq 'swap') {
796 delete $conf->{$opt};
797 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
798 "memory.memsw.limit_in_bytes", -1);
799 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
800 delete $conf->{$opt};
801 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
802 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
803 next if $hotplug_error->($opt);
804 delete $conf->{$opt};
805 } elsif ($opt eq 'cores') {
806 delete $conf->{$opt}; # rest is handled by pvestatd
807 } elsif ($opt eq 'cpulimit') {
808 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
809 delete $conf->{$opt};
810 } elsif ($opt eq 'cpuunits') {
811 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
812 delete $conf->{$opt};
813 } elsif ($opt =~ m/^net(\d)$/) {
814 delete $conf->{$opt};
817 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
818 } elsif ($opt eq 'protection') {
819 delete $conf->{$opt};
820 } elsif ($opt =~ m/^unused(\d+)$/) {
821 next if $hotplug_error->($opt);
822 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
823 push @deleted_volumes, $conf->{$opt};
824 delete $conf->{$opt};
825 } elsif ($opt =~ m/^mp(\d+)$/) {
826 next if $hotplug_error->($opt);
827 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
828 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
829 delete $conf->{$opt};
830 if ($mp->{type
} eq 'volume') {
831 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
833 } elsif ($opt eq 'unprivileged') {
834 die "unable to delete read-only option: '$opt'\n";
835 } elsif ($opt eq "replicate") {
836 delete $conf->{$opt};
838 die "implement me (delete: $opt)"
840 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
844 # There's no separate swap size to configure, there's memory and "total"
845 # memory (iow. memory+swap). This means we have to change them together.
846 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
847 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
848 if (defined($wanted_memory) || defined($wanted_swap)) {
850 my $old_memory = ($conf->{memory
} || 512);
851 my $old_swap = ($conf->{swap
} || 0);
853 $wanted_memory //= $old_memory;
854 $wanted_swap //= $old_swap;
856 my $total = $wanted_memory + $wanted_swap;
858 my $old_total = $old_memory + $old_swap;
859 if ($total > $old_total) {
860 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
861 "memory.memsw.limit_in_bytes",
862 int($total*1024*1024));
863 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
864 "memory.limit_in_bytes",
865 int($wanted_memory*1024*1024));
867 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
868 "memory.limit_in_bytes",
869 int($wanted_memory*1024*1024));
870 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
871 "memory.memsw.limit_in_bytes",
872 int($total*1024*1024));
875 $conf->{memory
} = $wanted_memory;
876 $conf->{swap
} = $wanted_swap;
878 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
881 my $storecfg = PVE
::Storage
::config
();
883 my $used_volids = {};
884 my $check_content_type = sub {
886 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
887 my $storage_config = PVE
::Storage
::storage_config
($storecfg, $sid);
888 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
889 if !$storage_config->{content
}->{rootdir
};
892 foreach my $opt (keys %$param) {
893 my $value = $param->{$opt};
894 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
895 if ($opt eq 'hostname' || $opt eq 'arch') {
896 $conf->{$opt} = $value;
897 } elsif ($opt eq 'onboot') {
898 $conf->{$opt} = $value ?
1 : 0;
899 } elsif ($opt eq 'startup') {
900 $conf->{$opt} = $value;
901 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
902 next if $hotplug_error->($opt);
903 $conf->{$opt} = $value;
904 } elsif ($opt eq 'nameserver') {
905 next if $hotplug_error->($opt);
906 my $list = PVE
::LXC
::verify_nameserver_list
($value);
907 $conf->{$opt} = $list;
908 } elsif ($opt eq 'searchdomain') {
909 next if $hotplug_error->($opt);
910 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
911 $conf->{$opt} = $list;
912 } elsif ($opt eq 'cores') {
913 $conf->{$opt} = $value;# rest is handled by pvestatd
914 } elsif ($opt eq 'cpulimit') {
916 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
918 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
920 $conf->{$opt} = $value;
921 } elsif ($opt eq 'cpuunits') {
922 $conf->{$opt} = $value;
923 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
924 } elsif ($opt eq 'description') {
925 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
926 } elsif ($opt =~ m/^net(\d+)$/) {
928 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
930 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
932 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
934 } elsif ($opt eq 'protection') {
935 $conf->{$opt} = $value ?
1 : 0;
936 } elsif ($opt =~ m/^mp(\d+)$/) {
937 next if $hotplug_error->($opt);
938 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
939 my $old = $conf->{$opt};
940 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
941 if ($mp->{type
} eq 'volume') {
942 &$check_content_type($mp);
943 $used_volids->{$mp->{volume
}} = 1;
945 $conf->{$opt} = $value;
947 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
948 if ($mp->{type
} eq 'volume') {
949 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
953 } elsif ($opt eq 'rootfs') {
954 next if $hotplug_error->($opt);
955 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
956 my $old = $conf->{$opt};
957 $conf->{$opt} = $value;
958 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
959 if ($mp->{type
} eq 'volume') {
960 &$check_content_type($mp);
961 $used_volids->{$mp->{volume
}} = 1;
964 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
965 if ($mp->{type
} eq 'volume') {
966 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
970 } elsif ($opt eq 'unprivileged') {
971 die "unable to modify read-only option: '$opt'\n";
972 } elsif ($opt eq 'ostype') {
973 next if $hotplug_error->($opt);
974 $conf->{$opt} = $value;
975 } elsif ($opt eq "replicate") {
976 my $repl = PVE
::JSONSchema
::check_format
('pve-replicate', $value);
977 PVE
::Cluster
::check_node_exists
($repl->{target
});
978 $conf->{$opt} = $value;
980 die "implement me: $opt";
983 if ($conf->{replicate
}) {
984 # check replicate feature on all mountpoints
985 PVE
::LXC
::Config-
>get_replicatable_volumes($storecfg, $conf);
988 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
991 # Apply deletions and creations of new volumes
992 if (@deleted_volumes) {
993 my $storage_cfg = PVE
::Storage
::config
();
994 foreach my $volume (@deleted_volumes) {
995 next if $used_volids->{$volume}; # could have been re-added, too
996 # also check for references in snapshots
997 next if $class->is_volume_in_use($conf, $volume, 1);
998 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1003 my $storage_cfg = PVE
::Storage
::config
();
1004 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
1007 # This should be the last thing we do here
1008 if ($running && scalar(@nohotplug)) {
1009 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1014 my ($class, $key, $value) = @_;
1016 die "unknown setting '$key'\n" if !$confdesc->{$key};
1018 my $type = $confdesc->{$key}->{type
};
1020 if (!defined($value)) {
1021 die "got undefined value\n";
1024 if ($value =~ m/[\n\r]/) {
1025 die "property contains a line feed\n";
1028 if ($type eq 'boolean') {
1029 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1030 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1031 die "type check ('boolean') failed - got '$value'\n";
1032 } elsif ($type eq 'integer') {
1033 return int($1) if $value =~ m/^(\d+)$/;
1034 die "type check ('integer') failed - got '$value'\n";
1035 } elsif ($type eq 'number') {
1036 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1037 die "type check ('number') failed - got '$value'\n";
1038 } elsif ($type eq 'string') {
1039 if (my $fmt = $confdesc->{$key}->{format
}) {
1040 PVE
::JSONSchema
::check_format
($fmt, $value);
1045 die "internal error"
1050 # add JSON properties for create and set function
1051 sub json_config_properties
{
1052 my ($class, $prop) = @_;
1054 foreach my $opt (keys %$confdesc) {
1055 next if $opt eq 'parent' || $opt eq 'snaptime';
1056 next if $prop->{$opt};
1057 $prop->{$opt} = $confdesc->{$opt};
1063 sub __parse_ct_mountpoint_full
{
1064 my ($class, $desc, $data, $noerr) = @_;
1069 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1071 return undef if $noerr;
1075 if (defined(my $size = $res->{size
})) {
1076 $size = PVE
::JSONSchema
::parse_size
($size);
1077 if (!defined($size)) {
1078 return undef if $noerr;
1079 die "invalid size: $size\n";
1081 $res->{size
} = $size;
1084 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1089 sub parse_ct_rootfs
{
1090 my ($class, $data, $noerr) = @_;
1092 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1094 $res->{mp
} = '/' if defined($res);
1099 sub parse_ct_mountpoint
{
1100 my ($class, $data, $noerr) = @_;
1102 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1105 sub print_ct_mountpoint
{
1106 my ($class, $info, $nomp) = @_;
1107 my $skip = [ 'type' ];
1108 push @$skip, 'mp' if $nomp;
1109 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1112 sub print_lxc_network
{
1113 my ($class, $net) = @_;
1114 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1117 sub parse_lxc_network
{
1118 my ($class, $data) = @_;
1122 return $res if !$data;
1124 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1126 $res->{type
} = 'veth';
1127 if (!$res->{hwaddr
}) {
1128 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1129 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1136 my ($class, $name) = @_;
1138 return defined($confdesc->{$name});
1140 # END JSON config code
1142 sub classify_mountpoint
{
1143 my ($class, $vol) = @_;
1144 if ($vol =~ m!^/!) {
1145 return 'device' if $vol =~ m!^/dev/!;
1151 sub is_volume_in_use
{
1152 my ($class, $config, $volid, $include_snapshots) = @_;
1155 $class->foreach_mountpoint($config, sub {
1156 my ($ms, $mountpoint) = @_;
1158 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1161 my $snapshots = $config->{snapshots
};
1162 if ($include_snapshots && $snapshots) {
1163 foreach my $snap (keys %$snapshots) {
1164 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1171 sub has_dev_console
{
1172 my ($class, $conf) = @_;
1174 return !(defined($conf->{console
}) && !$conf->{console
});
1178 my ($class, $conf, $keyname) = @_;
1180 if (my $lxcconf = $conf->{lxc
}) {
1181 foreach my $entry (@$lxcconf) {
1182 my ($key, undef) = @$entry;
1183 return 1 if $key eq $keyname;
1191 my ($class, $conf) = @_;
1193 return $conf->{tty
} // $confdesc->{tty
}->{default};
1197 my ($class, $conf) = @_;
1199 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1202 sub mountpoint_names
{
1203 my ($class, $reverse) = @_;
1205 my @names = ('rootfs');
1207 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1208 push @names, "mp$i";
1211 return $reverse ?
reverse @names : @names;
1214 sub foreach_mountpoint_full
{
1215 my ($class, $conf, $reverse, $func, @param) = @_;
1217 foreach my $key ($class->mountpoint_names($reverse)) {
1218 my $value = $conf->{$key};
1219 next if !defined($value);
1220 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1221 next if !defined($mountpoint);
1223 &$func($key, $mountpoint, @param);
1227 sub foreach_mountpoint
{
1228 my ($class, $conf, $func, @param) = @_;
1230 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1233 sub foreach_mountpoint_reverse
{
1234 my ($class, $conf, $func, @param) = @_;
1236 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1239 sub get_vm_volumes
{
1240 my ($class, $conf, $excludes) = @_;
1244 $class->foreach_mountpoint($conf, sub {
1245 my ($ms, $mountpoint) = @_;
1247 return if $excludes && $ms eq $excludes;
1249 my $volid = $mountpoint->{volume
};
1250 return if !$volid || $mountpoint->{type
} ne 'volume';
1252 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1255 push @$vollist, $volid;
1261 sub get_replicatable_volumes
{
1262 my ($class, $storecfg, $conf, $noerr) = @_;
1266 my $test_volid = sub {
1267 my ($volid, $mountpoint) = @_;
1271 return if defined($mountpoint->{replicate
}) && !$mountpoint->{replicate
};
1273 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1275 die "missing replicate feature on volume '$volid'\n";
1278 $volhash->{$volid} = 1;
1281 $class->foreach_mountpoint($conf, sub {
1282 my ($ms, $mountpoint) = @_;
1283 $test_volid->($mountpoint->{volume
}, $mountpoint);
1286 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1287 my $snap = $conf->{snapshots
}->{$snapname};
1288 $class->foreach_mountpoint($snap, sub {
1289 my ($ms, $mountpoint) = @_;
1290 $test_volid->($mountpoint->{volume
}, $mountpoint);