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 mountpoint',
245 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
250 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
251 type
=> 'string', format
=> $rootfs_desc,
252 description
=> "Use volume as container root.",
256 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
257 description
=> "The name of the snapshot.",
258 type
=> 'string', format
=> 'pve-configid',
266 description
=> "Lock/unlock the VM.",
267 enum
=> [qw(migrate backup snapshot rollback)],
272 description
=> "Specifies whether a VM will be started during system bootup.",
275 startup
=> get_standard_option
('pve-startup-order'),
279 description
=> "Enable/disable Template.",
285 enum
=> ['amd64', 'i386'],
286 description
=> "OS architecture type.",
292 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
293 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.",
298 description
=> "Attach a console device (/dev/console) to the container.",
304 description
=> "Specify the number of tty available to the container",
312 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
319 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.",
327 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.",
335 description
=> "Amount of RAM for the VM in MB.",
342 description
=> "Amount of SWAP for the VM in MB.",
348 description
=> "Set a host name for the container.",
349 type
=> 'string', format
=> 'dns-name',
355 description
=> "Container description. Only used on the configuration web interface.",
359 type
=> 'string', format
=> 'dns-name-list',
360 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
364 type
=> 'string', format
=> 'address-list',
365 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.",
367 rootfs
=> get_standard_option
('pve-ct-rootfs'),
370 type
=> 'string', format
=> 'pve-configid',
372 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
376 description
=> "Timestamp for snapshots.",
382 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).",
384 enum
=> ['shell', 'console', 'tty'],
390 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
396 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
401 my $valid_lxc_conf_keys = {
405 'lxc.haltsignal' => 1,
406 'lxc.rebootsignal' => 1,
407 'lxc.stopsignal' => 1,
409 'lxc.network.type' => 1,
410 'lxc.network.flags' => 1,
411 'lxc.network.link' => 1,
412 'lxc.network.mtu' => 1,
413 'lxc.network.name' => 1,
414 'lxc.network.hwaddr' => 1,
415 'lxc.network.ipv4' => 1,
416 'lxc.network.ipv4.gateway' => 1,
417 'lxc.network.ipv6' => 1,
418 'lxc.network.ipv6.gateway' => 1,
419 'lxc.network.script.up' => 1,
420 'lxc.network.script.down' => 1,
422 'lxc.console.logfile' => 1,
425 'lxc.devttydir' => 1,
426 'lxc.hook.autodev' => 1,
430 'lxc.mount.entry' => 1,
431 'lxc.mount.auto' => 1,
432 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
433 'lxc.rootfs.mount' => 1,
434 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
435 ', please use mountpoint options in the "rootfs" key',
439 'lxc.aa_profile' => 1,
440 'lxc.aa_allow_incomplete' => 1,
441 'lxc.se_context' => 1,
444 'lxc.hook.pre-start' => 1,
445 'lxc.hook.pre-mount' => 1,
446 'lxc.hook.mount' => 1,
447 'lxc.hook.start' => 1,
448 'lxc.hook.stop' => 1,
449 'lxc.hook.post-stop' => 1,
450 'lxc.hook.clone' => 1,
451 'lxc.hook.destroy' => 1,
454 'lxc.start.auto' => 1,
455 'lxc.start.delay' => 1,
456 'lxc.start.order' => 1,
458 'lxc.environment' => 1,
461 our $netconf_desc = {
465 description
=> "Network interface type.",
470 format_description
=> 'string',
471 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
472 pattern
=> '[-_.\w\d]+',
476 format_description
=> 'bridge',
477 description
=> 'Bridge to attach the network device to.',
478 pattern
=> '[-_.\w\d]+',
483 format_description
=> "XX:XX:XX:XX:XX:XX",
484 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)',
485 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
490 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
491 minimum
=> 64, # minimum ethernet frame is 64 bytes
496 format
=> 'pve-ipv4-config',
497 format_description
=> 'IPv4Format/CIDR',
498 description
=> 'IPv4 address in CIDR format.',
504 format_description
=> 'GatewayIPv4',
505 description
=> 'Default gateway for IPv4 traffic.',
510 format
=> 'pve-ipv6-config',
511 format_description
=> 'IPv6Format/CIDR',
512 description
=> 'IPv6 address in CIDR format.',
518 format_description
=> 'GatewayIPv6',
519 description
=> 'Default gateway for IPv6 traffic.',
524 description
=> "Controls whether this interface's firewall rules should be used.",
531 description
=> "VLAN tag for this interface.",
536 pattern
=> qr/\d+(?:;\d+)*/,
537 format_description
=> 'vlanid[;vlanid...]',
538 description
=> "VLAN ids to pass through the interface",
543 format_description
=> 'mbps',
544 description
=> "Apply rate limiting to the interface",
548 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
550 my $MAX_LXC_NETWORKS = 10;
551 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
552 $confdesc->{"net$i"} = {
554 type
=> 'string', format
=> $netconf_desc,
555 description
=> "Specifies network interfaces for the container.",
559 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
560 sub verify_lxc_mp_string
{
561 my ($mp, $noerr) = @_;
565 # /. or /.. at the end
566 # ../ at the beginning
568 if($mp =~ m
@/\.\
.?
/@ ||
571 return undef if $noerr;
572 die "$mp contains illegal character sequences\n";
581 description
=> 'Whether to include the mountpoint in backups.',
582 verbose_description
=> 'Whether to include the mountpoint in backups '.
583 '(only used for volume mountpoints).',
588 format
=> 'pve-lxc-mp-string',
589 format_description
=> 'Path',
590 description
=> 'Path to the mountpoint as seen from inside the container '.
591 '(must not contain symlinks).',
592 verbose_description
=> "Path to the mountpoint as seen from inside the container.\n\n".
593 "NOTE: Must not contain any symlinks for security reasons."
596 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
600 type
=> 'string', format
=> 'pve-volume-id',
601 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
604 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
605 $confdesc->{"mp$i"} = {
607 type
=> 'string', format
=> $mp_desc,
608 description
=> "Use volume as container mount point.",
613 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
614 $confdesc->{"unused$i"} = $unuseddesc;
617 sub parse_pct_config
{
618 my ($filename, $raw) = @_;
620 return undef if !defined($raw);
623 digest
=> Digest
::SHA
::sha1_hex
($raw),
627 $filename =~ m
|/lxc/(\d
+).conf
$|
628 || die "got strange filename '$filename'";
636 my @lines = split(/\n/, $raw);
637 foreach my $line (@lines) {
638 next if $line =~ m/^\s*$/;
640 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
642 $conf->{description
} = $descr if $descr;
644 $conf = $res->{snapshots
}->{$section} = {};
648 if ($line =~ m/^\#(.*)\s*$/) {
649 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
653 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
656 my $validity = $valid_lxc_conf_keys->{$key} || 0;
657 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
658 push @{$conf->{lxc
}}, [$key, $value];
659 } elsif (my $errmsg = $validity) {
660 warn "vm $vmid - $key: $errmsg\n";
662 warn "vm $vmid - unable to parse config: $line\n";
664 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
665 $descr .= PVE
::Tools
::decode_text
($2);
666 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
667 $conf->{snapstate
} = $1;
668 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
671 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
672 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
673 $conf->{$key} = $value;
675 warn "vm $vmid - unable to parse config: $line\n";
679 $conf->{description
} = $descr if $descr;
681 delete $res->{snapstate
}; # just to be sure
686 sub write_pct_config
{
687 my ($filename, $conf) = @_;
689 delete $conf->{snapstate
}; # just to be sure
691 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
692 my $used_volids = {};
693 foreach my $vid (@$volidlist) {
694 $used_volids->{$vid} = 1;
697 # remove 'unusedX' settings if the volume is still used
698 foreach my $key (keys %$conf) {
699 my $value = $conf->{$key};
700 if ($key =~ m/^unused/ && $used_volids->{$value}) {
701 delete $conf->{$key};
705 my $generate_raw_config = sub {
710 # add description as comment to top of file
711 my $descr = $conf->{description
} || '';
712 foreach my $cl (split(/\n/, $descr)) {
713 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
716 foreach my $key (sort keys %$conf) {
717 next if $key eq 'digest' || $key eq 'description' ||
718 $key eq 'pending' || $key eq 'snapshots' ||
719 $key eq 'snapname' || $key eq 'lxc';
720 my $value = $conf->{$key};
721 die "detected invalid newline inside property '$key'\n"
723 $raw .= "$key: $value\n";
726 if (my $lxcconf = $conf->{lxc
}) {
727 foreach my $entry (@$lxcconf) {
728 my ($k, $v) = @$entry;
736 my $raw = &$generate_raw_config($conf);
738 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
739 $raw .= "\n[$snapname]\n";
740 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
746 sub update_pct_config
{
747 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
756 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
757 $rootdir = "/proc/$pid/root";
760 my $hotplug_error = sub {
769 if (defined($delete)) {
770 foreach my $opt (@$delete) {
771 if (!exists($conf->{$opt})) {
772 warn "no such option: $opt\n";
776 if ($opt eq 'memory' || $opt eq 'rootfs') {
777 die "unable to delete required option '$opt'\n";
778 } elsif ($opt eq 'hostname') {
779 delete $conf->{$opt};
780 } elsif ($opt eq 'swap') {
781 delete $conf->{$opt};
782 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
783 "memory.memsw.limit_in_bytes", -1);
784 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
785 delete $conf->{$opt};
786 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
787 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
788 next if $hotplug_error->($opt);
789 delete $conf->{$opt};
790 } elsif ($opt eq 'cores') {
791 delete $conf->{$opt}; # rest is handled by pvestatd
792 } elsif ($opt eq 'cpulimit') {
793 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
794 delete $conf->{$opt};
795 } elsif ($opt eq 'cpuunits') {
796 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
797 delete $conf->{$opt};
798 } elsif ($opt =~ m/^net(\d)$/) {
799 delete $conf->{$opt};
802 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
803 } elsif ($opt eq 'protection') {
804 delete $conf->{$opt};
805 } elsif ($opt =~ m/^unused(\d+)$/) {
806 next if $hotplug_error->($opt);
807 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
808 push @deleted_volumes, $conf->{$opt};
809 delete $conf->{$opt};
810 } elsif ($opt =~ m/^mp(\d+)$/) {
811 next if $hotplug_error->($opt);
812 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
813 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
814 delete $conf->{$opt};
815 if ($mp->{type
} eq 'volume') {
816 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
818 } elsif ($opt eq 'unprivileged') {
819 die "unable to delete read-only option: '$opt'\n";
821 die "implement me (delete: $opt)"
823 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
827 # There's no separate swap size to configure, there's memory and "total"
828 # memory (iow. memory+swap). This means we have to change them together.
829 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
830 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
831 if (defined($wanted_memory) || defined($wanted_swap)) {
833 my $old_memory = ($conf->{memory
} || 512);
834 my $old_swap = ($conf->{swap
} || 0);
836 $wanted_memory //= $old_memory;
837 $wanted_swap //= $old_swap;
839 my $total = $wanted_memory + $wanted_swap;
841 my $old_total = $old_memory + $old_swap;
842 if ($total > $old_total) {
843 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
844 "memory.memsw.limit_in_bytes",
845 int($total*1024*1024));
846 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
847 "memory.limit_in_bytes",
848 int($wanted_memory*1024*1024));
850 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
851 "memory.limit_in_bytes",
852 int($wanted_memory*1024*1024));
853 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
854 "memory.memsw.limit_in_bytes",
855 int($total*1024*1024));
858 $conf->{memory
} = $wanted_memory;
859 $conf->{swap
} = $wanted_swap;
861 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
864 my $used_volids = {};
865 my $check_content_type = sub {
867 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
868 my $scfg = PVE
::Storage
::config
();
869 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
870 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
871 if !$storage_config->{content
}->{rootdir
};
874 foreach my $opt (keys %$param) {
875 my $value = $param->{$opt};
876 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
877 if ($opt eq 'hostname' || $opt eq 'arch') {
878 $conf->{$opt} = $value;
879 } elsif ($opt eq 'onboot') {
880 $conf->{$opt} = $value ?
1 : 0;
881 } elsif ($opt eq 'startup') {
882 $conf->{$opt} = $value;
883 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
884 next if $hotplug_error->($opt);
885 $conf->{$opt} = $value;
886 } elsif ($opt eq 'nameserver') {
887 next if $hotplug_error->($opt);
888 my $list = PVE
::LXC
::verify_nameserver_list
($value);
889 $conf->{$opt} = $list;
890 } elsif ($opt eq 'searchdomain') {
891 next if $hotplug_error->($opt);
892 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
893 $conf->{$opt} = $list;
894 } elsif ($opt eq 'cores') {
895 $conf->{$opt} = $value;# rest is handled by pvestatd
896 } elsif ($opt eq 'cpulimit') {
898 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
900 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
902 $conf->{$opt} = $value;
903 } elsif ($opt eq 'cpuunits') {
904 $conf->{$opt} = $value;
905 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
906 } elsif ($opt eq 'description') {
907 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
908 } elsif ($opt =~ m/^net(\d+)$/) {
910 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
912 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
914 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
916 } elsif ($opt eq 'protection') {
917 $conf->{$opt} = $value ?
1 : 0;
918 } elsif ($opt =~ m/^mp(\d+)$/) {
919 next if $hotplug_error->($opt);
920 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
921 my $old = $conf->{$opt};
922 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
923 if ($mp->{type
} eq 'volume') {
924 &$check_content_type($mp);
925 $used_volids->{$mp->{volume
}} = 1;
927 $conf->{$opt} = $value;
929 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
930 if ($mp->{type
} eq 'volume') {
931 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
935 } elsif ($opt eq 'rootfs') {
936 next if $hotplug_error->($opt);
937 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
938 my $old = $conf->{$opt};
939 $conf->{$opt} = $value;
940 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
941 if ($mp->{type
} eq 'volume') {
942 &$check_content_type($mp);
943 $used_volids->{$mp->{volume
}} = 1;
946 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
947 if ($mp->{type
} eq 'volume') {
948 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
952 } elsif ($opt eq 'unprivileged') {
953 die "unable to modify read-only option: '$opt'\n";
954 } elsif ($opt eq 'ostype') {
955 next if $hotplug_error->($opt);
956 $conf->{$opt} = $value;
958 die "implement me: $opt";
960 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
963 # Apply deletions and creations of new volumes
964 if (@deleted_volumes) {
965 my $storage_cfg = PVE
::Storage
::config
();
966 foreach my $volume (@deleted_volumes) {
967 next if $used_volids->{$volume}; # could have been re-added, too
968 # also check for references in snapshots
969 next if $class->is_volume_in_use($conf, $volume, 1);
970 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
975 my $storage_cfg = PVE
::Storage
::config
();
976 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
979 # This should be the last thing we do here
980 if ($running && scalar(@nohotplug)) {
981 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
986 my ($class, $key, $value) = @_;
988 die "unknown setting '$key'\n" if !$confdesc->{$key};
990 my $type = $confdesc->{$key}->{type
};
992 if (!defined($value)) {
993 die "got undefined value\n";
996 if ($value =~ m/[\n\r]/) {
997 die "property contains a line feed\n";
1000 if ($type eq 'boolean') {
1001 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1002 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1003 die "type check ('boolean') failed - got '$value'\n";
1004 } elsif ($type eq 'integer') {
1005 return int($1) if $value =~ m/^(\d+)$/;
1006 die "type check ('integer') failed - got '$value'\n";
1007 } elsif ($type eq 'number') {
1008 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1009 die "type check ('number') failed - got '$value'\n";
1010 } elsif ($type eq 'string') {
1011 if (my $fmt = $confdesc->{$key}->{format
}) {
1012 PVE
::JSONSchema
::check_format
($fmt, $value);
1017 die "internal error"
1022 # add JSON properties for create and set function
1023 sub json_config_properties
{
1024 my ($class, $prop) = @_;
1026 foreach my $opt (keys %$confdesc) {
1027 next if $opt eq 'parent' || $opt eq 'snaptime';
1028 next if $prop->{$opt};
1029 $prop->{$opt} = $confdesc->{$opt};
1035 sub __parse_ct_mountpoint_full
{
1036 my ($class, $desc, $data, $noerr) = @_;
1041 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1043 return undef if $noerr;
1047 if (defined(my $size = $res->{size
})) {
1048 $size = PVE
::JSONSchema
::parse_size
($size);
1049 if (!defined($size)) {
1050 return undef if $noerr;
1051 die "invalid size: $size\n";
1053 $res->{size
} = $size;
1056 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1061 sub parse_ct_rootfs
{
1062 my ($class, $data, $noerr) = @_;
1064 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1066 $res->{mp
} = '/' if defined($res);
1071 sub parse_ct_mountpoint
{
1072 my ($class, $data, $noerr) = @_;
1074 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1077 sub print_ct_mountpoint
{
1078 my ($class, $info, $nomp) = @_;
1079 my $skip = [ 'type' ];
1080 push @$skip, 'mp' if $nomp;
1081 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1084 sub print_lxc_network
{
1085 my ($class, $net) = @_;
1086 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1089 sub parse_lxc_network
{
1090 my ($class, $data) = @_;
1094 return $res if !$data;
1096 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1098 $res->{type
} = 'veth';
1099 if (!$res->{hwaddr
}) {
1100 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1101 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1108 my ($class, $name) = @_;
1110 return defined($confdesc->{$name});
1112 # END JSON config code
1114 sub classify_mountpoint
{
1115 my ($class, $vol) = @_;
1116 if ($vol =~ m!^/!) {
1117 return 'device' if $vol =~ m!^/dev/!;
1123 sub is_volume_in_use
{
1124 my ($class, $config, $volid, $include_snapshots) = @_;
1127 $class->foreach_mountpoint($config, sub {
1128 my ($ms, $mountpoint) = @_;
1130 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1133 my $snapshots = $config->{snapshots
};
1134 if ($include_snapshots && $snapshots) {
1135 foreach my $snap (keys %$snapshots) {
1136 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1143 sub has_dev_console
{
1144 my ($class, $conf) = @_;
1146 return !(defined($conf->{console
}) && !$conf->{console
});
1150 my ($class, $conf, $keyname) = @_;
1152 if (my $lxcconf = $conf->{lxc
}) {
1153 foreach my $entry (@$lxcconf) {
1154 my ($key, undef) = @$entry;
1155 return 1 if $key eq $keyname;
1163 my ($class, $conf) = @_;
1165 return $conf->{tty
} // $confdesc->{tty
}->{default};
1169 my ($class, $conf) = @_;
1171 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1174 sub mountpoint_names
{
1175 my ($class, $reverse) = @_;
1177 my @names = ('rootfs');
1179 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1180 push @names, "mp$i";
1183 return $reverse ?
reverse @names : @names;
1186 sub foreach_mountpoint_full
{
1187 my ($class, $conf, $reverse, $func, @param) = @_;
1189 foreach my $key ($class->mountpoint_names($reverse)) {
1190 my $value = $conf->{$key};
1191 next if !defined($value);
1192 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1193 next if !defined($mountpoint);
1195 &$func($key, $mountpoint, @param);
1199 sub foreach_mountpoint
{
1200 my ($class, $conf, $func, @param) = @_;
1202 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1205 sub foreach_mountpoint_reverse
{
1206 my ($class, $conf, $func, @param) = @_;
1208 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1211 sub get_vm_volumes
{
1212 my ($class, $conf, $excludes) = @_;
1216 $class->foreach_mountpoint($conf, sub {
1217 my ($ms, $mountpoint) = @_;
1219 return if $excludes && $ms eq $excludes;
1221 my $volid = $mountpoint->{volume
};
1222 return if !$volid || $mountpoint->{type
} ne 'volume';
1224 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1227 push @$vollist, $volid;