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";
48 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
51 $class->foreach_mountpoint($conf, sub {
52 my ($ms, $mountpoint) = @_;
54 return if $err; # skip further test
55 return if $backup_only && $ms ne 'rootfs' && !$mountpoint->{backup
};
58 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
59 $mountpoint->{volume
},
66 sub __snapshot_save_vmstate
{
67 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
68 die "implement me - snapshot_save_vmstate\n";
71 sub __snapshot_check_running
{
72 my ($class, $vmid) = @_;
73 return PVE
::LXC
::check_running
($vmid);
76 sub __snapshot_check_freeze_needed
{
77 my ($class, $vmid, $config, $save_vmstate) = @_;
79 my $ret = $class->__snapshot_check_running($vmid);
83 sub __snapshot_freeze
{
84 my ($class, $vmid, $unfreeze) = @_;
87 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
90 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
91 PVE
::LXC
::sync_container_namespace
($vmid);
95 sub __snapshot_create_vol_snapshot
{
96 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
98 my $storecfg = PVE
::Storage
::config
();
100 return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup
};
101 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
104 sub __snapshot_delete_remove_drive
{
105 my ($class, $snap, $remove_drive) = @_;
107 if ($remove_drive eq 'vmstate') {
108 die "implement me - saving vmstate\n";
110 my $value = $snap->{$remove_drive};
111 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
112 delete $snap->{$remove_drive};
113 $class->add_unused_volume($snap, $mountpoint->{volume
});
117 sub __snapshot_delete_vmstate_file
{
118 my ($class, $snap, $force) = @_;
120 die "implement me - saving vmstate\n";
123 sub __snapshot_delete_vol_snapshot
{
124 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
126 my $storecfg = PVE
::Storage
::config
();
127 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
128 push @$unused, $mountpoint->{volume
};
131 sub __snapshot_rollback_vol_possible
{
132 my ($class, $mountpoint, $snapname) = @_;
134 my $storecfg = PVE
::Storage
::config
();
135 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
138 sub __snapshot_rollback_vol_rollback
{
139 my ($class, $mountpoint, $snapname) = @_;
141 my $storecfg = PVE
::Storage
::config
();
142 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
145 sub __snapshot_rollback_vm_stop
{
146 my ($class, $vmid) = @_;
148 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
149 if $class->__snapshot_check_running($vmid);
152 sub __snapshot_rollback_vm_start
{
153 my ($class, $vmid, $vmstate, $forcemachine);
155 die "implement me - save vmstate\n";
158 sub __snapshot_foreach_volume
{
159 my ($class, $conf, $func) = @_;
161 $class->foreach_mountpoint($conf, $func);
164 # END implemented abstract methods from PVE::AbstractConfig
166 # BEGIN JSON config code
168 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
174 format
=> 'pve-lxc-mp-string',
175 format_description
=> 'volume',
176 description
=> 'Volume, device or directory to mount into the container.',
180 description
=> 'Whether to include the mountpoint in backups.',
185 format
=> 'disk-size',
186 format_description
=> 'DiskSize',
187 description
=> 'Volume size (read only value).',
192 description
=> 'Explicitly enable or disable ACL support.',
197 description
=> 'Read-only mountpoint (not supported with bind mounts)',
202 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
207 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
208 type
=> 'string', format
=> $rootfs_desc,
209 description
=> "Use volume as container root.",
213 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
214 description
=> "The name of the snapshot.",
215 type
=> 'string', format
=> 'pve-configid',
223 description
=> "Lock/unlock the VM.",
224 enum
=> [qw(migrate backup snapshot rollback)],
229 description
=> "Specifies whether a VM will be started during system bootup.",
232 startup
=> get_standard_option
('pve-startup-order'),
236 description
=> "Enable/disable Template.",
242 enum
=> ['amd64', 'i386'],
243 description
=> "OS architecture type.",
249 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
250 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.",
255 description
=> "Attach a console device (/dev/console) to the container.",
261 description
=> "Specify the number of tty available to the container",
269 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.",
277 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.",
285 description
=> "Amount of RAM for the VM in MB.",
292 description
=> "Amount of SWAP for the VM in MB.",
298 description
=> "Set a host name for the container.",
299 type
=> 'string', format
=> 'dns-name',
305 description
=> "Container description. Only used on the configuration web interface.",
309 type
=> 'string', format
=> 'dns-name-list',
310 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
314 type
=> 'string', format
=> 'address-list',
315 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.",
317 rootfs
=> get_standard_option
('pve-ct-rootfs'),
320 type
=> 'string', format
=> 'pve-configid',
322 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
326 description
=> "Timestamp for snapshots.",
332 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).",
334 enum
=> ['shell', 'console', 'tty'],
340 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
346 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
351 my $valid_lxc_conf_keys = {
355 'lxc.haltsignal' => 1,
356 'lxc.rebootsignal' => 1,
357 'lxc.stopsignal' => 1,
359 'lxc.network.type' => 1,
360 'lxc.network.flags' => 1,
361 'lxc.network.link' => 1,
362 'lxc.network.mtu' => 1,
363 'lxc.network.name' => 1,
364 'lxc.network.hwaddr' => 1,
365 'lxc.network.ipv4' => 1,
366 'lxc.network.ipv4.gateway' => 1,
367 'lxc.network.ipv6' => 1,
368 'lxc.network.ipv6.gateway' => 1,
369 'lxc.network.script.up' => 1,
370 'lxc.network.script.down' => 1,
372 'lxc.console.logfile' => 1,
375 'lxc.devttydir' => 1,
376 'lxc.hook.autodev' => 1,
380 'lxc.mount.entry' => 1,
381 'lxc.mount.auto' => 1,
382 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
383 'lxc.rootfs.mount' => 1,
384 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
385 ', please use mountpoint options in the "rootfs" key',
389 'lxc.aa_profile' => 1,
390 'lxc.aa_allow_incomplete' => 1,
391 'lxc.se_context' => 1,
394 'lxc.hook.pre-start' => 1,
395 'lxc.hook.pre-mount' => 1,
396 'lxc.hook.mount' => 1,
397 'lxc.hook.start' => 1,
398 'lxc.hook.stop' => 1,
399 'lxc.hook.post-stop' => 1,
400 'lxc.hook.clone' => 1,
401 'lxc.hook.destroy' => 1,
404 'lxc.start.auto' => 1,
405 'lxc.start.delay' => 1,
406 'lxc.start.order' => 1,
408 'lxc.environment' => 1,
411 our $netconf_desc = {
415 description
=> "Network interface type.",
420 format_description
=> 'string',
421 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
422 pattern
=> '[-_.\w\d]+',
426 format_description
=> 'bridge',
427 description
=> 'Bridge to attach the network device to.',
428 pattern
=> '[-_.\w\d]+',
433 format_description
=> "XX:XX:XX:XX:XX:XX",
434 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)',
435 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
440 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
441 minimum
=> 64, # minimum ethernet frame is 64 bytes
446 format
=> 'pve-ipv4-config',
447 format_description
=> 'IPv4Format/CIDR',
448 description
=> 'IPv4 address in CIDR format.',
454 format_description
=> 'GatewayIPv4',
455 description
=> 'Default gateway for IPv4 traffic.',
460 format
=> 'pve-ipv6-config',
461 format_description
=> 'IPv6Format/CIDR',
462 description
=> 'IPv6 address in CIDR format.',
468 format_description
=> 'GatewayIPv6',
469 description
=> 'Default gateway for IPv6 traffic.',
474 description
=> "Controls whether this interface's firewall rules should be used.",
481 description
=> "VLAN tag for this interface.",
486 pattern
=> qr/\d+(?:;\d+)*/,
487 format_description
=> 'vlanid[;vlanid...]',
488 description
=> "VLAN ids to pass through the interface",
493 format_description
=> 'mbps',
494 description
=> "Apply rate limiting to the interface",
498 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
500 my $MAX_LXC_NETWORKS = 10;
501 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
502 $confdesc->{"net$i"} = {
504 type
=> 'string', format
=> $netconf_desc,
505 description
=> "Specifies network interfaces for the container.",
509 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
510 sub verify_lxc_mp_string
{
511 my ($mp, $noerr) = @_;
515 # /. or /.. at the end
516 # ../ at the beginning
518 if($mp =~ m
@/\.\
.?
/@ ||
521 return undef if $noerr;
522 die "$mp contains illegal character sequences\n";
531 format
=> 'pve-lxc-mp-string',
532 format_description
=> 'Path',
533 description
=> 'Path to the mountpoint as seen from inside the container.',
536 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
540 type
=> 'string', format
=> 'pve-volume-id',
541 description
=> "Reference to unused volumes.",
544 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
545 $confdesc->{"mp$i"} = {
547 type
=> 'string', format
=> $mp_desc,
548 description
=> "Use volume as container mount point (experimental feature).",
553 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
554 $confdesc->{"unused$i"} = $unuseddesc;
557 sub parse_pct_config
{
558 my ($filename, $raw) = @_;
560 return undef if !defined($raw);
563 digest
=> Digest
::SHA
::sha1_hex
($raw),
567 $filename =~ m
|/lxc/(\d
+).conf
$|
568 || die "got strange filename '$filename'";
576 my @lines = split(/\n/, $raw);
577 foreach my $line (@lines) {
578 next if $line =~ m/^\s*$/;
580 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
582 $conf->{description
} = $descr if $descr;
584 $conf = $res->{snapshots
}->{$section} = {};
588 if ($line =~ m/^\#(.*)\s*$/) {
589 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
593 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
596 my $validity = $valid_lxc_conf_keys->{$key} || 0;
597 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
598 push @{$conf->{lxc
}}, [$key, $value];
599 } elsif (my $errmsg = $validity) {
600 warn "vm $vmid - $key: $errmsg\n";
602 warn "vm $vmid - unable to parse config: $line\n";
604 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
605 $descr .= PVE
::Tools
::decode_text
($2);
606 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
607 $conf->{snapstate
} = $1;
608 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
611 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
612 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
613 $conf->{$key} = $value;
615 warn "vm $vmid - unable to parse config: $line\n";
619 $conf->{description
} = $descr if $descr;
621 delete $res->{snapstate
}; # just to be sure
626 sub write_pct_config
{
627 my ($filename, $conf) = @_;
629 delete $conf->{snapstate
}; # just to be sure
631 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
632 my $used_volids = {};
633 foreach my $vid (@$volidlist) {
634 $used_volids->{$vid} = 1;
637 # remove 'unusedX' settings if the volume is still used
638 foreach my $key (keys %$conf) {
639 my $value = $conf->{$key};
640 if ($key =~ m/^unused/ && $used_volids->{$value}) {
641 delete $conf->{$key};
645 my $generate_raw_config = sub {
650 # add description as comment to top of file
651 my $descr = $conf->{description
} || '';
652 foreach my $cl (split(/\n/, $descr)) {
653 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
656 foreach my $key (sort keys %$conf) {
657 next if $key eq 'digest' || $key eq 'description' ||
658 $key eq 'pending' || $key eq 'snapshots' ||
659 $key eq 'snapname' || $key eq 'lxc';
660 my $value = $conf->{$key};
661 die "detected invalid newline inside property '$key'\n"
663 $raw .= "$key: $value\n";
666 if (my $lxcconf = $conf->{lxc
}) {
667 foreach my $entry (@$lxcconf) {
668 my ($k, $v) = @$entry;
676 my $raw = &$generate_raw_config($conf);
678 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
679 $raw .= "\n[$snapname]\n";
680 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
686 sub update_pct_config
{
687 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
696 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
697 $rootdir = "/proc/$pid/root";
700 my $hotplug_error = sub {
709 if (defined($delete)) {
710 foreach my $opt (@$delete) {
711 if (!exists($conf->{$opt})) {
712 warn "no such option: $opt\n";
716 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
717 die "unable to delete required option '$opt'\n";
718 } elsif ($opt eq 'swap') {
719 delete $conf->{$opt};
720 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
721 "memory.memsw.limit_in_bytes", -1);
722 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
723 delete $conf->{$opt};
724 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
725 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
726 next if $hotplug_error->($opt);
727 delete $conf->{$opt};
728 } elsif ($opt eq 'cpulimit') {
729 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
730 delete $conf->{$opt};
731 } elsif ($opt eq 'cpuunits') {
732 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
733 delete $conf->{$opt};
734 } elsif ($opt =~ m/^net(\d)$/) {
735 delete $conf->{$opt};
738 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
739 } elsif ($opt eq 'protection') {
740 delete $conf->{$opt};
741 } elsif ($opt =~ m/^unused(\d+)$/) {
742 next if $hotplug_error->($opt);
743 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
744 push @deleted_volumes, $conf->{$opt};
745 delete $conf->{$opt};
746 } elsif ($opt =~ m/^mp(\d+)$/) {
747 next if $hotplug_error->($opt);
748 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
749 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
750 delete $conf->{$opt};
751 if ($mp->{type
} eq 'volume') {
752 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
754 } elsif ($opt eq 'unprivileged') {
755 die "unable to delete read-only option: '$opt'\n";
757 die "implement me (delete: $opt)"
759 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
763 # There's no separate swap size to configure, there's memory and "total"
764 # memory (iow. memory+swap). This means we have to change them together.
765 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
766 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
767 if (defined($wanted_memory) || defined($wanted_swap)) {
769 my $old_memory = ($conf->{memory
} || 512);
770 my $old_swap = ($conf->{swap
} || 0);
772 $wanted_memory //= $old_memory;
773 $wanted_swap //= $old_swap;
775 my $total = $wanted_memory + $wanted_swap;
777 my $old_total = $old_memory + $old_swap;
778 if ($total > $old_total) {
779 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
780 "memory.memsw.limit_in_bytes",
781 int($total*1024*1024));
782 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
783 "memory.limit_in_bytes",
784 int($wanted_memory*1024*1024));
786 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
787 "memory.limit_in_bytes",
788 int($wanted_memory*1024*1024));
789 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
790 "memory.memsw.limit_in_bytes",
791 int($total*1024*1024));
794 $conf->{memory
} = $wanted_memory;
795 $conf->{swap
} = $wanted_swap;
797 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
800 my $used_volids = {};
801 my $check_content_type = sub {
803 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
804 my $scfg = PVE
::Storage
::config
();
805 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
806 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
807 if !$storage_config->{content
}->{rootdir
};
810 foreach my $opt (keys %$param) {
811 my $value = $param->{$opt};
812 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
813 if ($opt eq 'hostname') {
814 $conf->{$opt} = $value;
815 } elsif ($opt eq 'onboot') {
816 $conf->{$opt} = $value ?
1 : 0;
817 } elsif ($opt eq 'startup') {
818 $conf->{$opt} = $value;
819 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
820 next if $hotplug_error->($opt);
821 $conf->{$opt} = $value;
822 } elsif ($opt eq 'nameserver') {
823 next if $hotplug_error->($opt);
824 my $list = PVE
::LXC
::verify_nameserver_list
($value);
825 $conf->{$opt} = $list;
826 } elsif ($opt eq 'searchdomain') {
827 next if $hotplug_error->($opt);
828 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
829 $conf->{$opt} = $list;
830 } elsif ($opt eq 'cpulimit') {
831 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
832 $conf->{$opt} = $value;
833 } elsif ($opt eq 'cpuunits') {
834 $conf->{$opt} = $value;
835 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
836 } elsif ($opt eq 'description') {
837 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
838 } elsif ($opt =~ m/^net(\d+)$/) {
840 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
842 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
844 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
846 } elsif ($opt eq 'protection') {
847 $conf->{$opt} = $value ?
1 : 0;
848 } elsif ($opt =~ m/^mp(\d+)$/) {
849 next if $hotplug_error->($opt);
850 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
851 my $old = $conf->{$opt};
852 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
853 if ($mp->{type
} eq 'volume') {
854 &$check_content_type($mp);
855 $used_volids->{$mp->{volume
}} = 1;
857 $conf->{$opt} = $value;
859 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
860 if ($mp->{type
} eq 'volume') {
861 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
865 } elsif ($opt eq 'rootfs') {
866 next if $hotplug_error->($opt);
867 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
868 my $old = $conf->{$opt};
869 $conf->{$opt} = $value;
870 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
871 if ($mp->{type
} eq 'volume') {
872 &$check_content_type($mp);
873 $used_volids->{$mp->{volume
}} = 1;
876 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
877 if ($mp->{type
} eq 'volume') {
878 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
882 } elsif ($opt eq 'unprivileged') {
883 die "unable to modify read-only option: '$opt'\n";
884 } elsif ($opt eq 'ostype') {
885 next if $hotplug_error->($opt);
886 $conf->{$opt} = $value;
888 die "implement me: $opt";
890 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
893 # Apply deletions and creations of new volumes
894 if (@deleted_volumes) {
895 my $storage_cfg = PVE
::Storage
::config
();
896 foreach my $volume (@deleted_volumes) {
897 next if $used_volids->{$volume}; # could have been re-added, too
898 # also check for references in snapshots
899 next if $class->is_volume_in_use($conf, $volume, 1);
900 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
905 my $storage_cfg = PVE
::Storage
::config
();
906 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
909 # This should be the last thing we do here
910 if ($running && scalar(@nohotplug)) {
911 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
916 my ($class, $key, $value) = @_;
918 die "unknown setting '$key'\n" if !$confdesc->{$key};
920 my $type = $confdesc->{$key}->{type
};
922 if (!defined($value)) {
923 die "got undefined value\n";
926 if ($value =~ m/[\n\r]/) {
927 die "property contains a line feed\n";
930 if ($type eq 'boolean') {
931 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
932 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
933 die "type check ('boolean') failed - got '$value'\n";
934 } elsif ($type eq 'integer') {
935 return int($1) if $value =~ m/^(\d+)$/;
936 die "type check ('integer') failed - got '$value'\n";
937 } elsif ($type eq 'number') {
938 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
939 die "type check ('number') failed - got '$value'\n";
940 } elsif ($type eq 'string') {
941 if (my $fmt = $confdesc->{$key}->{format
}) {
942 PVE
::JSONSchema
::check_format
($fmt, $value);
952 # add JSON properties for create and set function
953 sub json_config_properties
{
954 my ($class, $prop) = @_;
956 foreach my $opt (keys %$confdesc) {
957 next if $opt eq 'parent' || $opt eq 'snaptime';
958 next if $prop->{$opt};
959 $prop->{$opt} = $confdesc->{$opt};
965 sub __parse_ct_mountpoint_full
{
966 my ($class, $desc, $data, $noerr) = @_;
971 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
973 return undef if $noerr;
977 if (defined(my $size = $res->{size
})) {
978 $size = PVE
::JSONSchema
::parse_size
($size);
979 if (!defined($size)) {
980 return undef if $noerr;
981 die "invalid size: $size\n";
983 $res->{size
} = $size;
986 $res->{type
} = $class->classify_mountpoint($res->{volume
});
991 sub parse_ct_rootfs
{
992 my ($class, $data, $noerr) = @_;
994 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
996 $res->{mp
} = '/' if defined($res);
1001 sub parse_ct_mountpoint
{
1002 my ($class, $data, $noerr) = @_;
1004 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1007 sub print_ct_mountpoint
{
1008 my ($class, $info, $nomp) = @_;
1009 my $skip = [ 'type' ];
1010 push @$skip, 'mp' if $nomp;
1011 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1014 sub print_lxc_network
{
1015 my ($class, $net) = @_;
1016 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1019 sub parse_lxc_network
{
1020 my ($class, $data) = @_;
1024 return $res if !$data;
1026 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1028 $res->{type
} = 'veth';
1029 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1035 my ($class, $name) = @_;
1037 return defined($confdesc->{$name});
1039 # END JSON config code
1041 sub classify_mountpoint
{
1042 my ($class, $vol) = @_;
1043 if ($vol =~ m!^/!) {
1044 return 'device' if $vol =~ m!^/dev/!;
1050 sub is_volume_in_use
{
1051 my ($class, $config, $volid, $include_snapshots) = @_;
1054 $class->foreach_mountpoint($config, sub {
1055 my ($ms, $mountpoint) = @_;
1057 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1060 my $snapshots = $config->{snapshots
};
1061 if ($include_snapshots && $snapshots) {
1062 foreach my $snap (keys %$snapshots) {
1063 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1070 sub has_dev_console
{
1071 my ($class, $conf) = @_;
1073 return !(defined($conf->{console
}) && !$conf->{console
});
1077 my ($class, $conf) = @_;
1079 return $conf->{tty
} // $confdesc->{tty
}->{default};
1083 my ($class, $conf) = @_;
1085 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1088 sub mountpoint_names
{
1089 my ($class, $reverse) = @_;
1091 my @names = ('rootfs');
1093 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1094 push @names, "mp$i";
1097 return $reverse ?
reverse @names : @names;
1100 sub foreach_mountpoint_full
{
1101 my ($class, $conf, $reverse, $func) = @_;
1103 foreach my $key ($class->mountpoint_names($reverse)) {
1104 my $value = $conf->{$key};
1105 next if !defined($value);
1106 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1107 next if !defined($mountpoint);
1109 &$func($key, $mountpoint);
1113 sub foreach_mountpoint
{
1114 my ($class, $conf, $func) = @_;
1116 $class->foreach_mountpoint_full($conf, 0, $func);
1119 sub foreach_mountpoint_reverse
{
1120 my ($class, $conf, $func) = @_;
1122 $class->foreach_mountpoint_full($conf, 1, $func);
1125 sub get_vm_volumes
{
1126 my ($class, $conf, $excludes) = @_;
1130 $class->foreach_mountpoint($conf, sub {
1131 my ($ms, $mountpoint) = @_;
1133 return if $excludes && $ms eq $excludes;
1135 my $volid = $mountpoint->{volume
};
1136 return if !$volid || $mountpoint->{type
} ne 'volume';
1138 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1141 push @$vollist, $volid;