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
=> "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.",
320 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.",
328 description
=> "Amount of RAM for the VM in MB.",
335 description
=> "Amount of SWAP for the VM in MB.",
341 description
=> "Set a host name for the container.",
342 type
=> 'string', format
=> 'dns-name',
348 description
=> "Container description. Only used on the configuration web interface.",
352 type
=> 'string', format
=> 'dns-name-list',
353 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
357 type
=> 'string', format
=> 'address-list',
358 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.",
360 rootfs
=> get_standard_option
('pve-ct-rootfs'),
363 type
=> 'string', format
=> 'pve-configid',
365 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
369 description
=> "Timestamp for snapshots.",
375 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).",
377 enum
=> ['shell', 'console', 'tty'],
383 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
389 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
394 my $valid_lxc_conf_keys = {
398 'lxc.haltsignal' => 1,
399 'lxc.rebootsignal' => 1,
400 'lxc.stopsignal' => 1,
402 'lxc.network.type' => 1,
403 'lxc.network.flags' => 1,
404 'lxc.network.link' => 1,
405 'lxc.network.mtu' => 1,
406 'lxc.network.name' => 1,
407 'lxc.network.hwaddr' => 1,
408 'lxc.network.ipv4' => 1,
409 'lxc.network.ipv4.gateway' => 1,
410 'lxc.network.ipv6' => 1,
411 'lxc.network.ipv6.gateway' => 1,
412 'lxc.network.script.up' => 1,
413 'lxc.network.script.down' => 1,
415 'lxc.console.logfile' => 1,
418 'lxc.devttydir' => 1,
419 'lxc.hook.autodev' => 1,
423 'lxc.mount.entry' => 1,
424 'lxc.mount.auto' => 1,
425 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
426 'lxc.rootfs.mount' => 1,
427 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
428 ', please use mountpoint options in the "rootfs" key',
432 'lxc.aa_profile' => 1,
433 'lxc.aa_allow_incomplete' => 1,
434 'lxc.se_context' => 1,
437 'lxc.hook.pre-start' => 1,
438 'lxc.hook.pre-mount' => 1,
439 'lxc.hook.mount' => 1,
440 'lxc.hook.start' => 1,
441 'lxc.hook.stop' => 1,
442 'lxc.hook.post-stop' => 1,
443 'lxc.hook.clone' => 1,
444 'lxc.hook.destroy' => 1,
447 'lxc.start.auto' => 1,
448 'lxc.start.delay' => 1,
449 'lxc.start.order' => 1,
451 'lxc.environment' => 1,
454 our $netconf_desc = {
458 description
=> "Network interface type.",
463 format_description
=> 'string',
464 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
465 pattern
=> '[-_.\w\d]+',
469 format_description
=> 'bridge',
470 description
=> 'Bridge to attach the network device to.',
471 pattern
=> '[-_.\w\d]+',
476 format_description
=> "XX:XX:XX:XX:XX:XX",
477 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)',
478 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
483 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
484 minimum
=> 64, # minimum ethernet frame is 64 bytes
489 format
=> 'pve-ipv4-config',
490 format_description
=> 'IPv4Format/CIDR',
491 description
=> 'IPv4 address in CIDR format.',
497 format_description
=> 'GatewayIPv4',
498 description
=> 'Default gateway for IPv4 traffic.',
503 format
=> 'pve-ipv6-config',
504 format_description
=> 'IPv6Format/CIDR',
505 description
=> 'IPv6 address in CIDR format.',
511 format_description
=> 'GatewayIPv6',
512 description
=> 'Default gateway for IPv6 traffic.',
517 description
=> "Controls whether this interface's firewall rules should be used.",
524 description
=> "VLAN tag for this interface.",
529 pattern
=> qr/\d+(?:;\d+)*/,
530 format_description
=> 'vlanid[;vlanid...]',
531 description
=> "VLAN ids to pass through the interface",
536 format_description
=> 'mbps',
537 description
=> "Apply rate limiting to the interface",
541 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
543 my $MAX_LXC_NETWORKS = 10;
544 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
545 $confdesc->{"net$i"} = {
547 type
=> 'string', format
=> $netconf_desc,
548 description
=> "Specifies network interfaces for the container.",
552 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
553 sub verify_lxc_mp_string
{
554 my ($mp, $noerr) = @_;
558 # /. or /.. at the end
559 # ../ at the beginning
561 if($mp =~ m
@/\.\
.?
/@ ||
564 return undef if $noerr;
565 die "$mp contains illegal character sequences\n";
574 description
=> 'Whether to include the mountpoint in backups.',
575 verbose_description
=> 'Whether to include the mountpoint in backups '.
576 '(only used for volume mountpoints).',
581 format
=> 'pve-lxc-mp-string',
582 format_description
=> 'Path',
583 description
=> 'Path to the mountpoint as seen from inside the container '.
584 '(must not contain symlinks).',
585 verbose_description
=> "Path to the mountpoint as seen from inside the container.\n\n".
586 "NOTE: Must not contain any symlinks for security reasons."
589 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
593 type
=> 'string', format
=> 'pve-volume-id',
594 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
597 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
598 $confdesc->{"mp$i"} = {
600 type
=> 'string', format
=> $mp_desc,
601 description
=> "Use volume as container mount point.",
606 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
607 $confdesc->{"unused$i"} = $unuseddesc;
610 sub parse_pct_config
{
611 my ($filename, $raw) = @_;
613 return undef if !defined($raw);
616 digest
=> Digest
::SHA
::sha1_hex
($raw),
620 $filename =~ m
|/lxc/(\d
+).conf
$|
621 || die "got strange filename '$filename'";
629 my @lines = split(/\n/, $raw);
630 foreach my $line (@lines) {
631 next if $line =~ m/^\s*$/;
633 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
635 $conf->{description
} = $descr if $descr;
637 $conf = $res->{snapshots
}->{$section} = {};
641 if ($line =~ m/^\#(.*)\s*$/) {
642 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
646 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
649 my $validity = $valid_lxc_conf_keys->{$key} || 0;
650 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
651 push @{$conf->{lxc
}}, [$key, $value];
652 } elsif (my $errmsg = $validity) {
653 warn "vm $vmid - $key: $errmsg\n";
655 warn "vm $vmid - unable to parse config: $line\n";
657 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
658 $descr .= PVE
::Tools
::decode_text
($2);
659 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
660 $conf->{snapstate
} = $1;
661 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
664 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
665 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
666 $conf->{$key} = $value;
668 warn "vm $vmid - unable to parse config: $line\n";
672 $conf->{description
} = $descr if $descr;
674 delete $res->{snapstate
}; # just to be sure
679 sub write_pct_config
{
680 my ($filename, $conf) = @_;
682 delete $conf->{snapstate
}; # just to be sure
684 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
685 my $used_volids = {};
686 foreach my $vid (@$volidlist) {
687 $used_volids->{$vid} = 1;
690 # remove 'unusedX' settings if the volume is still used
691 foreach my $key (keys %$conf) {
692 my $value = $conf->{$key};
693 if ($key =~ m/^unused/ && $used_volids->{$value}) {
694 delete $conf->{$key};
698 my $generate_raw_config = sub {
703 # add description as comment to top of file
704 my $descr = $conf->{description
} || '';
705 foreach my $cl (split(/\n/, $descr)) {
706 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
709 foreach my $key (sort keys %$conf) {
710 next if $key eq 'digest' || $key eq 'description' ||
711 $key eq 'pending' || $key eq 'snapshots' ||
712 $key eq 'snapname' || $key eq 'lxc';
713 my $value = $conf->{$key};
714 die "detected invalid newline inside property '$key'\n"
716 $raw .= "$key: $value\n";
719 if (my $lxcconf = $conf->{lxc
}) {
720 foreach my $entry (@$lxcconf) {
721 my ($k, $v) = @$entry;
729 my $raw = &$generate_raw_config($conf);
731 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
732 $raw .= "\n[$snapname]\n";
733 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
739 sub update_pct_config
{
740 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
749 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
750 $rootdir = "/proc/$pid/root";
753 my $hotplug_error = sub {
762 if (defined($delete)) {
763 foreach my $opt (@$delete) {
764 if (!exists($conf->{$opt})) {
765 warn "no such option: $opt\n";
769 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
770 die "unable to delete required option '$opt'\n";
771 } elsif ($opt eq 'swap') {
772 delete $conf->{$opt};
773 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
774 "memory.memsw.limit_in_bytes", -1);
775 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
776 delete $conf->{$opt};
777 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
778 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
779 next if $hotplug_error->($opt);
780 delete $conf->{$opt};
781 } elsif ($opt eq 'cpulimit') {
782 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
783 delete $conf->{$opt};
784 } elsif ($opt eq 'cpuunits') {
785 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
786 delete $conf->{$opt};
787 } elsif ($opt =~ m/^net(\d)$/) {
788 delete $conf->{$opt};
791 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
792 } elsif ($opt eq 'protection') {
793 delete $conf->{$opt};
794 } elsif ($opt =~ m/^unused(\d+)$/) {
795 next if $hotplug_error->($opt);
796 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
797 push @deleted_volumes, $conf->{$opt};
798 delete $conf->{$opt};
799 } elsif ($opt =~ m/^mp(\d+)$/) {
800 next if $hotplug_error->($opt);
801 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
802 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
803 delete $conf->{$opt};
804 if ($mp->{type
} eq 'volume') {
805 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
807 } elsif ($opt eq 'unprivileged') {
808 die "unable to delete read-only option: '$opt'\n";
810 die "implement me (delete: $opt)"
812 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
816 # There's no separate swap size to configure, there's memory and "total"
817 # memory (iow. memory+swap). This means we have to change them together.
818 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
819 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
820 if (defined($wanted_memory) || defined($wanted_swap)) {
822 my $old_memory = ($conf->{memory
} || 512);
823 my $old_swap = ($conf->{swap
} || 0);
825 $wanted_memory //= $old_memory;
826 $wanted_swap //= $old_swap;
828 my $total = $wanted_memory + $wanted_swap;
830 my $old_total = $old_memory + $old_swap;
831 if ($total > $old_total) {
832 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
833 "memory.memsw.limit_in_bytes",
834 int($total*1024*1024));
835 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
836 "memory.limit_in_bytes",
837 int($wanted_memory*1024*1024));
839 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
840 "memory.limit_in_bytes",
841 int($wanted_memory*1024*1024));
842 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
843 "memory.memsw.limit_in_bytes",
844 int($total*1024*1024));
847 $conf->{memory
} = $wanted_memory;
848 $conf->{swap
} = $wanted_swap;
850 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
853 my $used_volids = {};
854 my $check_content_type = sub {
856 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
857 my $scfg = PVE
::Storage
::config
();
858 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
859 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
860 if !$storage_config->{content
}->{rootdir
};
863 foreach my $opt (keys %$param) {
864 my $value = $param->{$opt};
865 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
866 if ($opt eq 'hostname') {
867 $conf->{$opt} = $value;
868 } elsif ($opt eq 'onboot') {
869 $conf->{$opt} = $value ?
1 : 0;
870 } elsif ($opt eq 'startup') {
871 $conf->{$opt} = $value;
872 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
873 next if $hotplug_error->($opt);
874 $conf->{$opt} = $value;
875 } elsif ($opt eq 'nameserver') {
876 next if $hotplug_error->($opt);
877 my $list = PVE
::LXC
::verify_nameserver_list
($value);
878 $conf->{$opt} = $list;
879 } elsif ($opt eq 'searchdomain') {
880 next if $hotplug_error->($opt);
881 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
882 $conf->{$opt} = $list;
883 } elsif ($opt eq 'cpulimit') {
884 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
885 $conf->{$opt} = $value;
886 } elsif ($opt eq 'cpuunits') {
887 $conf->{$opt} = $value;
888 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
889 } elsif ($opt eq 'description') {
890 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
891 } elsif ($opt =~ m/^net(\d+)$/) {
893 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
895 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
897 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
899 } elsif ($opt eq 'protection') {
900 $conf->{$opt} = $value ?
1 : 0;
901 } elsif ($opt =~ m/^mp(\d+)$/) {
902 next if $hotplug_error->($opt);
903 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
904 my $old = $conf->{$opt};
905 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
906 if ($mp->{type
} eq 'volume') {
907 &$check_content_type($mp);
908 $used_volids->{$mp->{volume
}} = 1;
910 $conf->{$opt} = $value;
912 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
913 if ($mp->{type
} eq 'volume') {
914 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
918 } elsif ($opt eq 'rootfs') {
919 next if $hotplug_error->($opt);
920 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
921 my $old = $conf->{$opt};
922 $conf->{$opt} = $value;
923 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
924 if ($mp->{type
} eq 'volume') {
925 &$check_content_type($mp);
926 $used_volids->{$mp->{volume
}} = 1;
929 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
930 if ($mp->{type
} eq 'volume') {
931 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
935 } elsif ($opt eq 'unprivileged') {
936 die "unable to modify read-only option: '$opt'\n";
937 } elsif ($opt eq 'ostype') {
938 next if $hotplug_error->($opt);
939 $conf->{$opt} = $value;
941 die "implement me: $opt";
943 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
946 # Apply deletions and creations of new volumes
947 if (@deleted_volumes) {
948 my $storage_cfg = PVE
::Storage
::config
();
949 foreach my $volume (@deleted_volumes) {
950 next if $used_volids->{$volume}; # could have been re-added, too
951 # also check for references in snapshots
952 next if $class->is_volume_in_use($conf, $volume, 1);
953 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
958 my $storage_cfg = PVE
::Storage
::config
();
959 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
962 # This should be the last thing we do here
963 if ($running && scalar(@nohotplug)) {
964 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
969 my ($class, $key, $value) = @_;
971 die "unknown setting '$key'\n" if !$confdesc->{$key};
973 my $type = $confdesc->{$key}->{type
};
975 if (!defined($value)) {
976 die "got undefined value\n";
979 if ($value =~ m/[\n\r]/) {
980 die "property contains a line feed\n";
983 if ($type eq 'boolean') {
984 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
985 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
986 die "type check ('boolean') failed - got '$value'\n";
987 } elsif ($type eq 'integer') {
988 return int($1) if $value =~ m/^(\d+)$/;
989 die "type check ('integer') failed - got '$value'\n";
990 } elsif ($type eq 'number') {
991 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
992 die "type check ('number') failed - got '$value'\n";
993 } elsif ($type eq 'string') {
994 if (my $fmt = $confdesc->{$key}->{format
}) {
995 PVE
::JSONSchema
::check_format
($fmt, $value);
1000 die "internal error"
1005 # add JSON properties for create and set function
1006 sub json_config_properties
{
1007 my ($class, $prop) = @_;
1009 foreach my $opt (keys %$confdesc) {
1010 next if $opt eq 'parent' || $opt eq 'snaptime';
1011 next if $prop->{$opt};
1012 $prop->{$opt} = $confdesc->{$opt};
1018 sub __parse_ct_mountpoint_full
{
1019 my ($class, $desc, $data, $noerr) = @_;
1024 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1026 return undef if $noerr;
1030 if (defined(my $size = $res->{size
})) {
1031 $size = PVE
::JSONSchema
::parse_size
($size);
1032 if (!defined($size)) {
1033 return undef if $noerr;
1034 die "invalid size: $size\n";
1036 $res->{size
} = $size;
1039 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1044 sub parse_ct_rootfs
{
1045 my ($class, $data, $noerr) = @_;
1047 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1049 $res->{mp
} = '/' if defined($res);
1054 sub parse_ct_mountpoint
{
1055 my ($class, $data, $noerr) = @_;
1057 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1060 sub print_ct_mountpoint
{
1061 my ($class, $info, $nomp) = @_;
1062 my $skip = [ 'type' ];
1063 push @$skip, 'mp' if $nomp;
1064 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1067 sub print_lxc_network
{
1068 my ($class, $net) = @_;
1069 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1072 sub parse_lxc_network
{
1073 my ($class, $data) = @_;
1077 return $res if !$data;
1079 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1081 $res->{type
} = 'veth';
1082 if (!$res->{hwaddr
}) {
1083 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1084 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1091 my ($class, $name) = @_;
1093 return defined($confdesc->{$name});
1095 # END JSON config code
1097 sub classify_mountpoint
{
1098 my ($class, $vol) = @_;
1099 if ($vol =~ m!^/!) {
1100 return 'device' if $vol =~ m!^/dev/!;
1106 sub is_volume_in_use
{
1107 my ($class, $config, $volid, $include_snapshots) = @_;
1110 $class->foreach_mountpoint($config, sub {
1111 my ($ms, $mountpoint) = @_;
1113 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1116 my $snapshots = $config->{snapshots
};
1117 if ($include_snapshots && $snapshots) {
1118 foreach my $snap (keys %$snapshots) {
1119 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1126 sub has_dev_console
{
1127 my ($class, $conf) = @_;
1129 return !(defined($conf->{console
}) && !$conf->{console
});
1133 my ($class, $conf) = @_;
1135 return $conf->{tty
} // $confdesc->{tty
}->{default};
1139 my ($class, $conf) = @_;
1141 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1144 sub mountpoint_names
{
1145 my ($class, $reverse) = @_;
1147 my @names = ('rootfs');
1149 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1150 push @names, "mp$i";
1153 return $reverse ?
reverse @names : @names;
1156 sub foreach_mountpoint_full
{
1157 my ($class, $conf, $reverse, $func, @param) = @_;
1159 foreach my $key ($class->mountpoint_names($reverse)) {
1160 my $value = $conf->{$key};
1161 next if !defined($value);
1162 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1163 next if !defined($mountpoint);
1165 &$func($key, $mountpoint, @param);
1169 sub foreach_mountpoint
{
1170 my ($class, $conf, $func, @param) = @_;
1172 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1175 sub foreach_mountpoint_reverse
{
1176 my ($class, $conf, $func, @param) = @_;
1178 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1181 sub get_vm_volumes
{
1182 my ($class, $conf, $excludes) = @_;
1186 $class->foreach_mountpoint($conf, sub {
1187 my ($ms, $mountpoint) = @_;
1189 return if $excludes && $ms eq $excludes;
1191 my $volid = $mountpoint->{volume
};
1192 return if !$volid || $mountpoint->{type
} ne 'volume';
1194 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1197 push @$vollist, $volid;