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) = @_;
126 my $storecfg = PVE
::Storage
::config
();
127 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
130 sub __snapshot_rollback_vol_possible
{
131 my ($class, $mountpoint, $snapname) = @_;
133 my $storecfg = PVE
::Storage
::config
();
134 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
137 sub __snapshot_rollback_vol_rollback
{
138 my ($class, $mountpoint, $snapname) = @_;
140 my $storecfg = PVE
::Storage
::config
();
141 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
144 sub __snapshot_rollback_vm_stop
{
145 my ($class, $vmid) = @_;
147 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
148 if $class->__snapshot_check_running($vmid);
151 sub __snapshot_rollback_vm_start
{
152 my ($class, $vmid, $vmstate, $forcemachine);
154 die "implement me - save vmstate\n";
157 sub __snapshot_foreach_volume
{
158 my ($class, $conf, $func) = @_;
160 $class->foreach_mountpoint($conf, $func);
163 # END implemented abstract methods from PVE::AbstractConfig
165 # BEGIN JSON config code
167 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
173 format
=> 'pve-lxc-mp-string',
174 format_description
=> 'volume',
175 description
=> 'Volume, device or directory to mount into the container.',
179 format_description
=> '[1|0]',
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 format_description
=> 'acl',
193 description
=> 'Explicitly enable or disable ACL support.',
198 format_description
=> 'ro',
199 description
=> 'Read-only mountpoint (not supported with bind mounts)',
204 format_description
=> '[0|1]',
205 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
210 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
211 type
=> 'string', format
=> $rootfs_desc,
212 description
=> "Use volume as container root.",
216 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
217 description
=> "The name of the snapshot.",
218 type
=> 'string', format
=> 'pve-configid',
226 description
=> "Lock/unlock the VM.",
227 enum
=> [qw(migrate backup snapshot rollback)],
232 description
=> "Specifies whether a VM will be started during system bootup.",
235 startup
=> get_standard_option
('pve-startup-order'),
239 description
=> "Enable/disable Template.",
245 enum
=> ['amd64', 'i386'],
246 description
=> "OS architecture type.",
252 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
253 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.",
258 description
=> "Attach a console device (/dev/console) to the container.",
264 description
=> "Specify the number of tty available to the container",
272 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
280 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.",
288 description
=> "Amount of RAM for the VM in MB.",
295 description
=> "Amount of SWAP for the VM in MB.",
301 description
=> "Set a host name for the container.",
302 type
=> 'string', format
=> 'dns-name',
308 description
=> "Container description. Only used on the configuration web interface.",
312 type
=> 'string', format
=> 'dns-name-list',
313 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
317 type
=> 'string', format
=> 'address-list',
318 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.",
320 rootfs
=> get_standard_option
('pve-ct-rootfs'),
323 type
=> 'string', format
=> 'pve-configid',
325 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
329 description
=> "Timestamp for snapshots.",
335 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).",
337 enum
=> ['shell', 'console', 'tty'],
343 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
349 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
354 my $valid_lxc_conf_keys = {
358 'lxc.haltsignal' => 1,
359 'lxc.rebootsignal' => 1,
360 'lxc.stopsignal' => 1,
362 'lxc.network.type' => 1,
363 'lxc.network.flags' => 1,
364 'lxc.network.link' => 1,
365 'lxc.network.mtu' => 1,
366 'lxc.network.name' => 1,
367 'lxc.network.hwaddr' => 1,
368 'lxc.network.ipv4' => 1,
369 'lxc.network.ipv4.gateway' => 1,
370 'lxc.network.ipv6' => 1,
371 'lxc.network.ipv6.gateway' => 1,
372 'lxc.network.script.up' => 1,
373 'lxc.network.script.down' => 1,
375 'lxc.console.logfile' => 1,
378 'lxc.devttydir' => 1,
379 'lxc.hook.autodev' => 1,
383 'lxc.mount.entry' => 1,
384 'lxc.mount.auto' => 1,
385 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
386 'lxc.rootfs.mount' => 1,
387 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
388 ', please use mountpoint options in the "rootfs" key',
392 'lxc.aa_profile' => 1,
393 'lxc.aa_allow_incomplete' => 1,
394 'lxc.se_context' => 1,
397 'lxc.hook.pre-start' => 1,
398 'lxc.hook.pre-mount' => 1,
399 'lxc.hook.mount' => 1,
400 'lxc.hook.start' => 1,
401 'lxc.hook.stop' => 1,
402 'lxc.hook.post-stop' => 1,
403 'lxc.hook.clone' => 1,
404 'lxc.hook.destroy' => 1,
407 'lxc.start.auto' => 1,
408 'lxc.start.delay' => 1,
409 'lxc.start.order' => 1,
411 'lxc.environment' => 1,
418 description
=> "Network interface type.",
423 format_description
=> 'String',
424 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
425 pattern
=> '[-_.\w\d]+',
429 format_description
=> 'vmbr<Number>',
430 description
=> 'Bridge to attach the network device to.',
431 pattern
=> '[-_.\w\d]+',
436 format_description
=> 'MAC',
437 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
438 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
443 format_description
=> 'Number',
444 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
445 minimum
=> 64, # minimum ethernet frame is 64 bytes
450 format
=> 'pve-ipv4-config',
451 format_description
=> 'IPv4Format/CIDR',
452 description
=> 'IPv4 address in CIDR format.',
458 format_description
=> 'GatewayIPv4',
459 description
=> 'Default gateway for IPv4 traffic.',
464 format
=> 'pve-ipv6-config',
465 format_description
=> 'IPv6Format/CIDR',
466 description
=> 'IPv6 address in CIDR format.',
472 format_description
=> 'GatewayIPv6',
473 description
=> 'Default gateway for IPv6 traffic.',
478 format_description
=> '[1|0]',
479 description
=> "Controls whether this interface's firewall rules should be used.",
484 format_description
=> 'VlanNo',
487 description
=> "VLAN tag for this interface.",
492 pattern
=> qr/\d+(?:;\d+)*/,
493 format_description
=> 'vlanid[;vlanid...]',
494 description
=> "VLAN ids to pass through 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 =~ m/^net(\d)$/) {
729 delete $conf->{$opt};
732 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
733 } elsif ($opt eq 'protection') {
734 delete $conf->{$opt};
735 } elsif ($opt =~ m/^unused(\d+)$/) {
736 next if $hotplug_error->($opt);
737 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
738 push @deleted_volumes, $conf->{$opt};
739 delete $conf->{$opt};
740 } elsif ($opt =~ m/^mp(\d+)$/) {
741 next if $hotplug_error->($opt);
742 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
743 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
744 delete $conf->{$opt};
745 if ($mp->{type
} eq 'volume') {
746 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
748 } elsif ($opt eq 'unprivileged') {
749 die "unable to delete read-only option: '$opt'\n";
751 die "implement me (delete: $opt)"
753 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
757 # There's no separate swap size to configure, there's memory and "total"
758 # memory (iow. memory+swap). This means we have to change them together.
759 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
760 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
761 if (defined($wanted_memory) || defined($wanted_swap)) {
763 my $old_memory = ($conf->{memory
} || 512);
764 my $old_swap = ($conf->{swap
} || 0);
766 $wanted_memory //= $old_memory;
767 $wanted_swap //= $old_swap;
769 my $total = $wanted_memory + $wanted_swap;
771 my $old_total = $old_memory + $old_swap;
772 if ($total > $old_total) {
773 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
774 "memory.memsw.limit_in_bytes",
775 int($total*1024*1024));
776 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
777 "memory.limit_in_bytes",
778 int($wanted_memory*1024*1024));
780 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
781 "memory.limit_in_bytes",
782 int($wanted_memory*1024*1024));
783 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
784 "memory.memsw.limit_in_bytes",
785 int($total*1024*1024));
788 $conf->{memory
} = $wanted_memory;
789 $conf->{swap
} = $wanted_swap;
791 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
794 my $used_volids = {};
796 foreach my $opt (keys %$param) {
797 my $value = $param->{$opt};
798 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
799 if ($opt eq 'hostname') {
800 $conf->{$opt} = $value;
801 } elsif ($opt eq 'onboot') {
802 $conf->{$opt} = $value ?
1 : 0;
803 } elsif ($opt eq 'startup') {
804 $conf->{$opt} = $value;
805 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
806 next if $hotplug_error->($opt);
807 $conf->{$opt} = $value;
808 } elsif ($opt eq 'nameserver') {
809 next if $hotplug_error->($opt);
810 my $list = PVE
::LXC
::verify_nameserver_list
($value);
811 $conf->{$opt} = $list;
812 } elsif ($opt eq 'searchdomain') {
813 next if $hotplug_error->($opt);
814 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
815 $conf->{$opt} = $list;
816 } elsif ($opt eq 'cpulimit') {
817 next if $hotplug_error->($opt); # FIXME: hotplug
818 $conf->{$opt} = $value;
819 } elsif ($opt eq 'cpuunits') {
820 $conf->{$opt} = $value;
821 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
822 } elsif ($opt eq 'description') {
823 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
824 } elsif ($opt =~ m/^net(\d+)$/) {
826 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
828 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
830 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
832 } elsif ($opt eq 'protection') {
833 $conf->{$opt} = $value ?
1 : 0;
834 } elsif ($opt =~ m/^mp(\d+)$/) {
835 next if $hotplug_error->($opt);
836 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
837 my $old = $conf->{$opt};
838 $conf->{$opt} = $value;
840 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
841 if ($mp->{type
} eq 'volume') {
842 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
846 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
847 $used_volids->{$mp->{volume
}} = 1;
848 } elsif ($opt eq 'rootfs') {
849 next if $hotplug_error->($opt);
850 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
851 my $old = $conf->{$opt};
852 $conf->{$opt} = $value;
854 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
855 if ($mp->{type
} eq 'volume') {
856 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
859 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
860 $used_volids->{$mp->{volume
}} = 1;
861 } elsif ($opt eq 'unprivileged') {
862 die "unable to modify read-only option: '$opt'\n";
863 } elsif ($opt eq 'ostype') {
864 next if $hotplug_error->($opt);
865 $conf->{$opt} = $value;
867 die "implement me: $opt";
869 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
872 # Apply deletions and creations of new volumes
873 if (@deleted_volumes) {
874 my $storage_cfg = PVE
::Storage
::config
();
875 foreach my $volume (@deleted_volumes) {
876 next if $used_volids->{$volume}; # could have been re-added, too
877 # also check for references in snapshots
878 next if $class->is_volume_in_use($conf, $volume, 1);
879 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
884 my $storage_cfg = PVE
::Storage
::config
();
885 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
888 # This should be the last thing we do here
889 if ($running && scalar(@nohotplug)) {
890 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
895 my ($class, $key, $value) = @_;
897 die "unknown setting '$key'\n" if !$confdesc->{$key};
899 my $type = $confdesc->{$key}->{type
};
901 if (!defined($value)) {
902 die "got undefined value\n";
905 if ($value =~ m/[\n\r]/) {
906 die "property contains a line feed\n";
909 if ($type eq 'boolean') {
910 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
911 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
912 die "type check ('boolean') failed - got '$value'\n";
913 } elsif ($type eq 'integer') {
914 return int($1) if $value =~ m/^(\d+)$/;
915 die "type check ('integer') failed - got '$value'\n";
916 } elsif ($type eq 'number') {
917 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
918 die "type check ('number') failed - got '$value'\n";
919 } elsif ($type eq 'string') {
920 if (my $fmt = $confdesc->{$key}->{format
}) {
921 PVE
::JSONSchema
::check_format
($fmt, $value);
931 # add JSON properties for create and set function
932 sub json_config_properties
{
933 my ($class, $prop) = @_;
935 foreach my $opt (keys %$confdesc) {
936 next if $opt eq 'parent' || $opt eq 'snaptime';
937 next if $prop->{$opt};
938 $prop->{$opt} = $confdesc->{$opt};
944 sub __parse_ct_mountpoint_full
{
945 my ($class, $desc, $data, $noerr) = @_;
950 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
952 return undef if $noerr;
956 if (defined(my $size = $res->{size
})) {
957 $size = PVE
::JSONSchema
::parse_size
($size);
958 if (!defined($size)) {
959 return undef if $noerr;
960 die "invalid size: $size\n";
962 $res->{size
} = $size;
965 $res->{type
} = $class->classify_mountpoint($res->{volume
});
970 sub parse_ct_rootfs
{
971 my ($class, $data, $noerr) = @_;
973 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
975 $res->{mp
} = '/' if defined($res);
980 sub parse_ct_mountpoint
{
981 my ($class, $data, $noerr) = @_;
983 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
986 sub print_ct_mountpoint
{
987 my ($class, $info, $nomp) = @_;
988 my $skip = [ 'type' ];
989 push @$skip, 'mp' if $nomp;
990 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
993 sub print_lxc_network
{
994 my ($class, $net) = @_;
995 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
998 sub parse_lxc_network
{
999 my ($class, $data) = @_;
1003 return $res if !$data;
1005 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1007 $res->{type
} = 'veth';
1008 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1014 my ($class, $name) = @_;
1016 return defined($confdesc->{$name});
1018 # END JSON config code
1020 sub classify_mountpoint
{
1021 my ($class, $vol) = @_;
1022 if ($vol =~ m!^/!) {
1023 return 'device' if $vol =~ m!^/dev/!;
1029 sub is_volume_in_use
{
1030 my ($class, $config, $volid, $include_snapshots) = @_;
1033 $class->foreach_mountpoint($config, sub {
1034 my ($ms, $mountpoint) = @_;
1036 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1039 my $snapshots = $config->{snapshots
};
1040 if ($include_snapshots && $snapshots) {
1041 foreach my $snap (keys %$snapshots) {
1042 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1049 sub has_dev_console
{
1050 my ($class, $conf) = @_;
1052 return !(defined($conf->{console
}) && !$conf->{console
});
1056 my ($class, $conf) = @_;
1058 return $conf->{tty
} // $confdesc->{tty
}->{default};
1062 my ($class, $conf) = @_;
1064 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1067 sub mountpoint_names
{
1068 my ($class, $reverse) = @_;
1070 my @names = ('rootfs');
1072 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1073 push @names, "mp$i";
1076 return $reverse ?
reverse @names : @names;
1079 sub foreach_mountpoint_full
{
1080 my ($class, $conf, $reverse, $func) = @_;
1082 foreach my $key ($class->mountpoint_names($reverse)) {
1083 my $value = $conf->{$key};
1084 next if !defined($value);
1085 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1086 next if !defined($mountpoint);
1088 &$func($key, $mountpoint);
1092 sub foreach_mountpoint
{
1093 my ($class, $conf, $func) = @_;
1095 $class->foreach_mountpoint_full($conf, 0, $func);
1098 sub foreach_mountpoint_reverse
{
1099 my ($class, $conf, $func) = @_;
1101 $class->foreach_mountpoint_full($conf, 1, $func);
1104 sub get_vm_volumes
{
1105 my ($class, $conf, $excludes) = @_;
1109 $class->foreach_mountpoint($conf, sub {
1110 my ($ms, $mountpoint) = @_;
1112 return if $excludes && $ms eq $excludes;
1114 my $volid = $mountpoint->{volume
};
1115 return if !$volid || $mountpoint->{type
} ne 'volume';
1117 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1120 push @$vollist, $volid;