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 format_description
=> '[1|0]',
181 description
=> 'Whether to include the mountpoint in backups.',
186 format
=> 'disk-size',
187 format_description
=> 'DiskSize',
188 description
=> 'Volume size (read only value).',
193 format_description
=> 'acl',
194 description
=> 'Explicitly enable or disable ACL support.',
199 format_description
=> 'ro',
200 description
=> 'Read-only mountpoint (not supported with bind mounts)',
205 format_description
=> '[0|1]',
206 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
211 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
212 type
=> 'string', format
=> $rootfs_desc,
213 description
=> "Use volume as container root.",
217 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
218 description
=> "The name of the snapshot.",
219 type
=> 'string', format
=> 'pve-configid',
227 description
=> "Lock/unlock the VM.",
228 enum
=> [qw(migrate backup snapshot rollback)],
233 description
=> "Specifies whether a VM will be started during system bootup.",
236 startup
=> get_standard_option
('pve-startup-order'),
240 description
=> "Enable/disable Template.",
246 enum
=> ['amd64', 'i386'],
247 description
=> "OS architecture type.",
253 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
254 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.",
259 description
=> "Attach a console device (/dev/console) to the container.",
265 description
=> "Specify the number of tty available to the container",
273 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.",
281 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.",
289 description
=> "Amount of RAM for the VM in MB.",
296 description
=> "Amount of SWAP for the VM in MB.",
302 description
=> "Set a host name for the container.",
303 type
=> 'string', format
=> 'dns-name',
309 description
=> "Container description. Only used on the configuration web interface.",
313 type
=> 'string', format
=> 'dns-name-list',
314 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
318 type
=> 'string', format
=> 'address-list',
319 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.",
321 rootfs
=> get_standard_option
('pve-ct-rootfs'),
324 type
=> 'string', format
=> 'pve-configid',
326 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
330 description
=> "Timestamp for snapshots.",
336 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).",
338 enum
=> ['shell', 'console', 'tty'],
344 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
350 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
355 my $valid_lxc_conf_keys = {
359 'lxc.haltsignal' => 1,
360 'lxc.rebootsignal' => 1,
361 'lxc.stopsignal' => 1,
363 'lxc.network.type' => 1,
364 'lxc.network.flags' => 1,
365 'lxc.network.link' => 1,
366 'lxc.network.mtu' => 1,
367 'lxc.network.name' => 1,
368 'lxc.network.hwaddr' => 1,
369 'lxc.network.ipv4' => 1,
370 'lxc.network.ipv4.gateway' => 1,
371 'lxc.network.ipv6' => 1,
372 'lxc.network.ipv6.gateway' => 1,
373 'lxc.network.script.up' => 1,
374 'lxc.network.script.down' => 1,
376 'lxc.console.logfile' => 1,
379 'lxc.devttydir' => 1,
380 'lxc.hook.autodev' => 1,
384 'lxc.mount.entry' => 1,
385 'lxc.mount.auto' => 1,
386 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
387 'lxc.rootfs.mount' => 1,
388 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
389 ', please use mountpoint options in the "rootfs" key',
393 'lxc.aa_profile' => 1,
394 'lxc.aa_allow_incomplete' => 1,
395 'lxc.se_context' => 1,
398 'lxc.hook.pre-start' => 1,
399 'lxc.hook.pre-mount' => 1,
400 'lxc.hook.mount' => 1,
401 'lxc.hook.start' => 1,
402 'lxc.hook.stop' => 1,
403 'lxc.hook.post-stop' => 1,
404 'lxc.hook.clone' => 1,
405 'lxc.hook.destroy' => 1,
408 'lxc.start.auto' => 1,
409 'lxc.start.delay' => 1,
410 'lxc.start.order' => 1,
412 'lxc.environment' => 1,
415 our $netconf_desc = {
419 description
=> "Network interface type.",
424 format_description
=> 'String',
425 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
426 pattern
=> '[-_.\w\d]+',
430 format_description
=> 'vmbr<Number>',
431 description
=> 'Bridge to attach the network device to.',
432 pattern
=> '[-_.\w\d]+',
437 format_description
=> 'MAC',
438 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)',
439 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
444 format_description
=> 'Number',
445 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
446 minimum
=> 64, # minimum ethernet frame is 64 bytes
451 format
=> 'pve-ipv4-config',
452 format_description
=> 'IPv4Format/CIDR',
453 description
=> 'IPv4 address in CIDR format.',
459 format_description
=> 'GatewayIPv4',
460 description
=> 'Default gateway for IPv4 traffic.',
465 format
=> 'pve-ipv6-config',
466 format_description
=> 'IPv6Format/CIDR',
467 description
=> 'IPv6 address in CIDR format.',
473 format_description
=> 'GatewayIPv6',
474 description
=> 'Default gateway for IPv6 traffic.',
479 format_description
=> '[1|0]',
480 description
=> "Controls whether this interface's firewall rules should be used.",
485 format_description
=> 'VlanNo',
488 description
=> "VLAN tag for this interface.",
493 pattern
=> qr/\d+(?:;\d+)*/,
494 format_description
=> 'vlanid[;vlanid...]',
495 description
=> "VLAN ids to pass through the interface",
500 format_description
=> 'mbps',
501 description
=> "Apply rate limiting to the interface",
505 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
507 my $MAX_LXC_NETWORKS = 10;
508 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
509 $confdesc->{"net$i"} = {
511 type
=> 'string', format
=> $netconf_desc,
512 description
=> "Specifies network interfaces for the container.",
516 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
517 sub verify_lxc_mp_string
{
518 my ($mp, $noerr) = @_;
522 # /. or /.. at the end
523 # ../ at the beginning
525 if($mp =~ m
@/\.\
.?
/@ ||
528 return undef if $noerr;
529 die "$mp contains illegal character sequences\n";
538 format
=> 'pve-lxc-mp-string',
539 format_description
=> 'Path',
540 description
=> 'Path to the mountpoint as seen from inside the container.',
543 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
547 type
=> 'string', format
=> 'pve-volume-id',
548 description
=> "Reference to unused volumes.",
551 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
552 $confdesc->{"mp$i"} = {
554 type
=> 'string', format
=> $mp_desc,
555 description
=> "Use volume as container mount point (experimental feature).",
560 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
561 $confdesc->{"unused$i"} = $unuseddesc;
564 sub parse_pct_config
{
565 my ($filename, $raw) = @_;
567 return undef if !defined($raw);
570 digest
=> Digest
::SHA
::sha1_hex
($raw),
574 $filename =~ m
|/lxc/(\d
+).conf
$|
575 || die "got strange filename '$filename'";
583 my @lines = split(/\n/, $raw);
584 foreach my $line (@lines) {
585 next if $line =~ m/^\s*$/;
587 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
589 $conf->{description
} = $descr if $descr;
591 $conf = $res->{snapshots
}->{$section} = {};
595 if ($line =~ m/^\#(.*)\s*$/) {
596 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
600 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
603 my $validity = $valid_lxc_conf_keys->{$key} || 0;
604 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
605 push @{$conf->{lxc
}}, [$key, $value];
606 } elsif (my $errmsg = $validity) {
607 warn "vm $vmid - $key: $errmsg\n";
609 warn "vm $vmid - unable to parse config: $line\n";
611 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
612 $descr .= PVE
::Tools
::decode_text
($2);
613 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
614 $conf->{snapstate
} = $1;
615 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
618 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
619 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
620 $conf->{$key} = $value;
622 warn "vm $vmid - unable to parse config: $line\n";
626 $conf->{description
} = $descr if $descr;
628 delete $res->{snapstate
}; # just to be sure
633 sub write_pct_config
{
634 my ($filename, $conf) = @_;
636 delete $conf->{snapstate
}; # just to be sure
638 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
639 my $used_volids = {};
640 foreach my $vid (@$volidlist) {
641 $used_volids->{$vid} = 1;
644 # remove 'unusedX' settings if the volume is still used
645 foreach my $key (keys %$conf) {
646 my $value = $conf->{$key};
647 if ($key =~ m/^unused/ && $used_volids->{$value}) {
648 delete $conf->{$key};
652 my $generate_raw_config = sub {
657 # add description as comment to top of file
658 my $descr = $conf->{description
} || '';
659 foreach my $cl (split(/\n/, $descr)) {
660 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
663 foreach my $key (sort keys %$conf) {
664 next if $key eq 'digest' || $key eq 'description' ||
665 $key eq 'pending' || $key eq 'snapshots' ||
666 $key eq 'snapname' || $key eq 'lxc';
667 my $value = $conf->{$key};
668 die "detected invalid newline inside property '$key'\n"
670 $raw .= "$key: $value\n";
673 if (my $lxcconf = $conf->{lxc
}) {
674 foreach my $entry (@$lxcconf) {
675 my ($k, $v) = @$entry;
683 my $raw = &$generate_raw_config($conf);
685 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
686 $raw .= "\n[$snapname]\n";
687 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
693 sub update_pct_config
{
694 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
703 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
704 $rootdir = "/proc/$pid/root";
707 my $hotplug_error = sub {
716 if (defined($delete)) {
717 foreach my $opt (@$delete) {
718 if (!exists($conf->{$opt})) {
719 warn "no such option: $opt\n";
723 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
724 die "unable to delete required option '$opt'\n";
725 } elsif ($opt eq 'swap') {
726 delete $conf->{$opt};
727 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
728 "memory.memsw.limit_in_bytes", -1);
729 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
730 delete $conf->{$opt};
731 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
732 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
733 next if $hotplug_error->($opt);
734 delete $conf->{$opt};
735 } elsif ($opt eq 'cpulimit') {
736 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
737 delete $conf->{$opt};
738 } elsif ($opt eq 'cpuunits') {
739 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
740 delete $conf->{$opt};
741 } elsif ($opt =~ m/^net(\d)$/) {
742 delete $conf->{$opt};
745 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
746 } elsif ($opt eq 'protection') {
747 delete $conf->{$opt};
748 } elsif ($opt =~ m/^unused(\d+)$/) {
749 next if $hotplug_error->($opt);
750 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
751 push @deleted_volumes, $conf->{$opt};
752 delete $conf->{$opt};
753 } elsif ($opt =~ m/^mp(\d+)$/) {
754 next if $hotplug_error->($opt);
755 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
756 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
757 delete $conf->{$opt};
758 if ($mp->{type
} eq 'volume') {
759 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
761 } elsif ($opt eq 'unprivileged') {
762 die "unable to delete read-only option: '$opt'\n";
764 die "implement me (delete: $opt)"
766 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
770 # There's no separate swap size to configure, there's memory and "total"
771 # memory (iow. memory+swap). This means we have to change them together.
772 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
773 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
774 if (defined($wanted_memory) || defined($wanted_swap)) {
776 my $old_memory = ($conf->{memory
} || 512);
777 my $old_swap = ($conf->{swap
} || 0);
779 $wanted_memory //= $old_memory;
780 $wanted_swap //= $old_swap;
782 my $total = $wanted_memory + $wanted_swap;
784 my $old_total = $old_memory + $old_swap;
785 if ($total > $old_total) {
786 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
787 "memory.memsw.limit_in_bytes",
788 int($total*1024*1024));
789 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
790 "memory.limit_in_bytes",
791 int($wanted_memory*1024*1024));
793 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
794 "memory.limit_in_bytes",
795 int($wanted_memory*1024*1024));
796 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
797 "memory.memsw.limit_in_bytes",
798 int($total*1024*1024));
801 $conf->{memory
} = $wanted_memory;
802 $conf->{swap
} = $wanted_swap;
804 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
807 my $used_volids = {};
808 my $check_content_type = sub {
810 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
811 my $scfg = PVE
::Storage
::config
();
812 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
813 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
814 if !$storage_config->{content
}->{rootdir
};
817 foreach my $opt (keys %$param) {
818 my $value = $param->{$opt};
819 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
820 if ($opt eq 'hostname') {
821 $conf->{$opt} = $value;
822 } elsif ($opt eq 'onboot') {
823 $conf->{$opt} = $value ?
1 : 0;
824 } elsif ($opt eq 'startup') {
825 $conf->{$opt} = $value;
826 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
827 next if $hotplug_error->($opt);
828 $conf->{$opt} = $value;
829 } elsif ($opt eq 'nameserver') {
830 next if $hotplug_error->($opt);
831 my $list = PVE
::LXC
::verify_nameserver_list
($value);
832 $conf->{$opt} = $list;
833 } elsif ($opt eq 'searchdomain') {
834 next if $hotplug_error->($opt);
835 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
836 $conf->{$opt} = $list;
837 } elsif ($opt eq 'cpulimit') {
838 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
839 $conf->{$opt} = $value;
840 } elsif ($opt eq 'cpuunits') {
841 $conf->{$opt} = $value;
842 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
843 } elsif ($opt eq 'description') {
844 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
845 } elsif ($opt =~ m/^net(\d+)$/) {
847 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
849 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
851 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
853 } elsif ($opt eq 'protection') {
854 $conf->{$opt} = $value ?
1 : 0;
855 } elsif ($opt =~ m/^mp(\d+)$/) {
856 next if $hotplug_error->($opt);
857 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
858 my $old = $conf->{$opt};
859 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
860 if ($mp->{type
} eq 'volume') {
861 &$check_content_type($mp);
862 $used_volids->{$mp->{volume
}} = 1;
864 $conf->{$opt} = $value;
866 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
867 if ($mp->{type
} eq 'volume') {
868 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
872 } elsif ($opt eq 'rootfs') {
873 next if $hotplug_error->($opt);
874 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
875 my $old = $conf->{$opt};
876 $conf->{$opt} = $value;
877 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
878 if ($mp->{type
} eq 'volume') {
879 &$check_content_type($mp);
880 $used_volids->{$mp->{volume
}} = 1;
883 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
884 if ($mp->{type
} eq 'volume') {
885 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
889 } elsif ($opt eq 'unprivileged') {
890 die "unable to modify read-only option: '$opt'\n";
891 } elsif ($opt eq 'ostype') {
892 next if $hotplug_error->($opt);
893 $conf->{$opt} = $value;
895 die "implement me: $opt";
897 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
900 # Apply deletions and creations of new volumes
901 if (@deleted_volumes) {
902 my $storage_cfg = PVE
::Storage
::config
();
903 foreach my $volume (@deleted_volumes) {
904 next if $used_volids->{$volume}; # could have been re-added, too
905 # also check for references in snapshots
906 next if $class->is_volume_in_use($conf, $volume, 1);
907 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
912 my $storage_cfg = PVE
::Storage
::config
();
913 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
916 # This should be the last thing we do here
917 if ($running && scalar(@nohotplug)) {
918 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
923 my ($class, $key, $value) = @_;
925 die "unknown setting '$key'\n" if !$confdesc->{$key};
927 my $type = $confdesc->{$key}->{type
};
929 if (!defined($value)) {
930 die "got undefined value\n";
933 if ($value =~ m/[\n\r]/) {
934 die "property contains a line feed\n";
937 if ($type eq 'boolean') {
938 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
939 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
940 die "type check ('boolean') failed - got '$value'\n";
941 } elsif ($type eq 'integer') {
942 return int($1) if $value =~ m/^(\d+)$/;
943 die "type check ('integer') failed - got '$value'\n";
944 } elsif ($type eq 'number') {
945 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
946 die "type check ('number') failed - got '$value'\n";
947 } elsif ($type eq 'string') {
948 if (my $fmt = $confdesc->{$key}->{format
}) {
949 PVE
::JSONSchema
::check_format
($fmt, $value);
959 # add JSON properties for create and set function
960 sub json_config_properties
{
961 my ($class, $prop) = @_;
963 foreach my $opt (keys %$confdesc) {
964 next if $opt eq 'parent' || $opt eq 'snaptime';
965 next if $prop->{$opt};
966 $prop->{$opt} = $confdesc->{$opt};
972 sub __parse_ct_mountpoint_full
{
973 my ($class, $desc, $data, $noerr) = @_;
978 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
980 return undef if $noerr;
984 if (defined(my $size = $res->{size
})) {
985 $size = PVE
::JSONSchema
::parse_size
($size);
986 if (!defined($size)) {
987 return undef if $noerr;
988 die "invalid size: $size\n";
990 $res->{size
} = $size;
993 $res->{type
} = $class->classify_mountpoint($res->{volume
});
998 sub parse_ct_rootfs
{
999 my ($class, $data, $noerr) = @_;
1001 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1003 $res->{mp
} = '/' if defined($res);
1008 sub parse_ct_mountpoint
{
1009 my ($class, $data, $noerr) = @_;
1011 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1014 sub print_ct_mountpoint
{
1015 my ($class, $info, $nomp) = @_;
1016 my $skip = [ 'type' ];
1017 push @$skip, 'mp' if $nomp;
1018 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1021 sub print_lxc_network
{
1022 my ($class, $net) = @_;
1023 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1026 sub parse_lxc_network
{
1027 my ($class, $data) = @_;
1031 return $res if !$data;
1033 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1035 $res->{type
} = 'veth';
1036 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1042 my ($class, $name) = @_;
1044 return defined($confdesc->{$name});
1046 # END JSON config code
1048 sub classify_mountpoint
{
1049 my ($class, $vol) = @_;
1050 if ($vol =~ m!^/!) {
1051 return 'device' if $vol =~ m!^/dev/!;
1057 sub is_volume_in_use
{
1058 my ($class, $config, $volid, $include_snapshots) = @_;
1061 $class->foreach_mountpoint($config, sub {
1062 my ($ms, $mountpoint) = @_;
1064 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1067 my $snapshots = $config->{snapshots
};
1068 if ($include_snapshots && $snapshots) {
1069 foreach my $snap (keys %$snapshots) {
1070 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1077 sub has_dev_console
{
1078 my ($class, $conf) = @_;
1080 return !(defined($conf->{console
}) && !$conf->{console
});
1084 my ($class, $conf) = @_;
1086 return $conf->{tty
} // $confdesc->{tty
}->{default};
1090 my ($class, $conf) = @_;
1092 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1095 sub mountpoint_names
{
1096 my ($class, $reverse) = @_;
1098 my @names = ('rootfs');
1100 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1101 push @names, "mp$i";
1104 return $reverse ?
reverse @names : @names;
1107 sub foreach_mountpoint_full
{
1108 my ($class, $conf, $reverse, $func) = @_;
1110 foreach my $key ($class->mountpoint_names($reverse)) {
1111 my $value = $conf->{$key};
1112 next if !defined($value);
1113 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1114 next if !defined($mountpoint);
1116 &$func($key, $mountpoint);
1120 sub foreach_mountpoint
{
1121 my ($class, $conf, $func) = @_;
1123 $class->foreach_mountpoint_full($conf, 0, $func);
1126 sub foreach_mountpoint_reverse
{
1127 my ($class, $conf, $func) = @_;
1129 $class->foreach_mountpoint_full($conf, 1, $func);
1132 sub get_vm_volumes
{
1133 my ($class, $conf, $excludes) = @_;
1137 $class->foreach_mountpoint($conf, sub {
1138 my ($ms, $mountpoint) = @_;
1140 return if $excludes && $ms eq $excludes;
1142 my $volid = $mountpoint->{volume
};
1143 return if !$volid || $mountpoint->{type
} ne 'volume';
1145 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1148 push @$vollist, $volid;