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. Note 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,
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
=> 'Bridge to attach the network device to. (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",
499 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
501 my $MAX_LXC_NETWORKS = 10;
502 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
503 $confdesc->{"net$i"} = {
505 type
=> 'string', format
=> $netconf_desc,
506 description
=> "Specifies network interfaces for the container.",
510 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
511 sub verify_lxc_mp_string
{
512 my ($mp, $noerr) = @_;
516 # /. or /.. at the end
517 # ../ at the beginning
519 if($mp =~ m
@/\.\
.?
/@ ||
522 return undef if $noerr;
523 die "$mp contains illegal character sequences\n";
532 format
=> 'pve-lxc-mp-string',
533 format_description
=> 'Path',
534 description
=> 'Path to the mountpoint as seen from inside the container.',
537 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
541 type
=> 'string', format
=> 'pve-volume-id',
542 description
=> "Reference to unused volumes.",
545 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
546 $confdesc->{"mp$i"} = {
548 type
=> 'string', format
=> $mp_desc,
549 description
=> "Use volume as container mount point (experimental feature).",
554 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
555 $confdesc->{"unused$i"} = $unuseddesc;
558 sub parse_pct_config
{
559 my ($filename, $raw) = @_;
561 return undef if !defined($raw);
564 digest
=> Digest
::SHA
::sha1_hex
($raw),
568 $filename =~ m
|/lxc/(\d
+).conf
$|
569 || die "got strange filename '$filename'";
577 my @lines = split(/\n/, $raw);
578 foreach my $line (@lines) {
579 next if $line =~ m/^\s*$/;
581 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
583 $conf->{description
} = $descr if $descr;
585 $conf = $res->{snapshots
}->{$section} = {};
589 if ($line =~ m/^\#(.*)\s*$/) {
590 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
594 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
597 my $validity = $valid_lxc_conf_keys->{$key} || 0;
598 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
599 push @{$conf->{lxc
}}, [$key, $value];
600 } elsif (my $errmsg = $validity) {
601 warn "vm $vmid - $key: $errmsg\n";
603 warn "vm $vmid - unable to parse config: $line\n";
605 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
606 $descr .= PVE
::Tools
::decode_text
($2);
607 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
608 $conf->{snapstate
} = $1;
609 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
612 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
613 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
614 $conf->{$key} = $value;
616 warn "vm $vmid - unable to parse config: $line\n";
620 $conf->{description
} = $descr if $descr;
622 delete $res->{snapstate
}; # just to be sure
627 sub write_pct_config
{
628 my ($filename, $conf) = @_;
630 delete $conf->{snapstate
}; # just to be sure
632 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
633 my $used_volids = {};
634 foreach my $vid (@$volidlist) {
635 $used_volids->{$vid} = 1;
638 # remove 'unusedX' settings if the volume is still used
639 foreach my $key (keys %$conf) {
640 my $value = $conf->{$key};
641 if ($key =~ m/^unused/ && $used_volids->{$value}) {
642 delete $conf->{$key};
646 my $generate_raw_config = sub {
651 # add description as comment to top of file
652 my $descr = $conf->{description
} || '';
653 foreach my $cl (split(/\n/, $descr)) {
654 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
657 foreach my $key (sort keys %$conf) {
658 next if $key eq 'digest' || $key eq 'description' ||
659 $key eq 'pending' || $key eq 'snapshots' ||
660 $key eq 'snapname' || $key eq 'lxc';
661 my $value = $conf->{$key};
662 die "detected invalid newline inside property '$key'\n"
664 $raw .= "$key: $value\n";
667 if (my $lxcconf = $conf->{lxc
}) {
668 foreach my $entry (@$lxcconf) {
669 my ($k, $v) = @$entry;
677 my $raw = &$generate_raw_config($conf);
679 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
680 $raw .= "\n[$snapname]\n";
681 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
687 sub update_pct_config
{
688 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
697 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
698 $rootdir = "/proc/$pid/root";
701 my $hotplug_error = sub {
710 if (defined($delete)) {
711 foreach my $opt (@$delete) {
712 if (!exists($conf->{$opt})) {
713 warn "no such option: $opt\n";
717 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
718 die "unable to delete required option '$opt'\n";
719 } elsif ($opt eq 'swap') {
720 delete $conf->{$opt};
721 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
722 "memory.memsw.limit_in_bytes", -1);
723 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
724 delete $conf->{$opt};
725 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
726 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
727 next if $hotplug_error->($opt);
728 delete $conf->{$opt};
729 } elsif ($opt =~ m/^net(\d)$/) {
730 delete $conf->{$opt};
733 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
734 } elsif ($opt eq 'protection') {
735 delete $conf->{$opt};
736 } elsif ($opt =~ m/^unused(\d+)$/) {
737 next if $hotplug_error->($opt);
738 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
739 push @deleted_volumes, $conf->{$opt};
740 delete $conf->{$opt};
741 } elsif ($opt =~ m/^mp(\d+)$/) {
742 next if $hotplug_error->($opt);
743 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
744 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
745 delete $conf->{$opt};
746 if ($mp->{type
} eq 'volume') {
747 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
749 } elsif ($opt eq 'unprivileged') {
750 die "unable to delete read-only option: '$opt'\n";
752 die "implement me (delete: $opt)"
754 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
758 # There's no separate swap size to configure, there's memory and "total"
759 # memory (iow. memory+swap). This means we have to change them together.
760 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
761 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
762 if (defined($wanted_memory) || defined($wanted_swap)) {
764 my $old_memory = ($conf->{memory
} || 512);
765 my $old_swap = ($conf->{swap
} || 0);
767 $wanted_memory //= $old_memory;
768 $wanted_swap //= $old_swap;
770 my $total = $wanted_memory + $wanted_swap;
772 my $old_total = $old_memory + $old_swap;
773 if ($total > $old_total) {
774 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
775 "memory.memsw.limit_in_bytes",
776 int($total*1024*1024));
777 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
778 "memory.limit_in_bytes",
779 int($wanted_memory*1024*1024));
781 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
782 "memory.limit_in_bytes",
783 int($wanted_memory*1024*1024));
784 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
785 "memory.memsw.limit_in_bytes",
786 int($total*1024*1024));
789 $conf->{memory
} = $wanted_memory;
790 $conf->{swap
} = $wanted_swap;
792 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
795 my $used_volids = {};
797 foreach my $opt (keys %$param) {
798 my $value = $param->{$opt};
799 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
800 if ($opt eq 'hostname') {
801 $conf->{$opt} = $value;
802 } elsif ($opt eq 'onboot') {
803 $conf->{$opt} = $value ?
1 : 0;
804 } elsif ($opt eq 'startup') {
805 $conf->{$opt} = $value;
806 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
807 next if $hotplug_error->($opt);
808 $conf->{$opt} = $value;
809 } elsif ($opt eq 'nameserver') {
810 next if $hotplug_error->($opt);
811 my $list = PVE
::LXC
::verify_nameserver_list
($value);
812 $conf->{$opt} = $list;
813 } elsif ($opt eq 'searchdomain') {
814 next if $hotplug_error->($opt);
815 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
816 $conf->{$opt} = $list;
817 } elsif ($opt eq 'cpulimit') {
818 next if $hotplug_error->($opt); # FIXME: hotplug
819 $conf->{$opt} = $value;
820 } elsif ($opt eq 'cpuunits') {
821 $conf->{$opt} = $value;
822 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
823 } elsif ($opt eq 'description') {
824 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
825 } elsif ($opt =~ m/^net(\d+)$/) {
827 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
829 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
831 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
833 } elsif ($opt eq 'protection') {
834 $conf->{$opt} = $value ?
1 : 0;
835 } elsif ($opt =~ m/^mp(\d+)$/) {
836 next if $hotplug_error->($opt);
837 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
838 my $old = $conf->{$opt};
839 $conf->{$opt} = $value;
841 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
842 if ($mp->{type
} eq 'volume') {
843 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
847 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
848 $used_volids->{$mp->{volume
}} = 1;
849 } elsif ($opt eq 'rootfs') {
850 next if $hotplug_error->($opt);
851 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
852 my $old = $conf->{$opt};
853 $conf->{$opt} = $value;
855 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
856 if ($mp->{type
} eq 'volume') {
857 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
860 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
861 $used_volids->{$mp->{volume
}} = 1;
862 } elsif ($opt eq 'unprivileged') {
863 die "unable to modify read-only option: '$opt'\n";
864 } elsif ($opt eq 'ostype') {
865 next if $hotplug_error->($opt);
866 $conf->{$opt} = $value;
868 die "implement me: $opt";
870 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
873 # Apply deletions and creations of new volumes
874 if (@deleted_volumes) {
875 my $storage_cfg = PVE
::Storage
::config
();
876 foreach my $volume (@deleted_volumes) {
877 next if $used_volids->{$volume}; # could have been re-added, too
878 # also check for references in snapshots
879 next if $class->is_volume_in_use($conf, $volume, 1);
880 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
885 my $storage_cfg = PVE
::Storage
::config
();
886 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
889 # This should be the last thing we do here
890 if ($running && scalar(@nohotplug)) {
891 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
896 my ($class, $key, $value) = @_;
898 die "unknown setting '$key'\n" if !$confdesc->{$key};
900 my $type = $confdesc->{$key}->{type
};
902 if (!defined($value)) {
903 die "got undefined value\n";
906 if ($value =~ m/[\n\r]/) {
907 die "property contains a line feed\n";
910 if ($type eq 'boolean') {
911 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
912 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
913 die "type check ('boolean') failed - got '$value'\n";
914 } elsif ($type eq 'integer') {
915 return int($1) if $value =~ m/^(\d+)$/;
916 die "type check ('integer') failed - got '$value'\n";
917 } elsif ($type eq 'number') {
918 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
919 die "type check ('number') failed - got '$value'\n";
920 } elsif ($type eq 'string') {
921 if (my $fmt = $confdesc->{$key}->{format
}) {
922 PVE
::JSONSchema
::check_format
($fmt, $value);
932 # add JSON properties for create and set function
933 sub json_config_properties
{
934 my ($class, $prop) = @_;
936 foreach my $opt (keys %$confdesc) {
937 next if $opt eq 'parent' || $opt eq 'snaptime';
938 next if $prop->{$opt};
939 $prop->{$opt} = $confdesc->{$opt};
945 sub __parse_ct_mountpoint_full
{
946 my ($class, $desc, $data, $noerr) = @_;
951 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
953 return undef if $noerr;
957 if (defined(my $size = $res->{size
})) {
958 $size = PVE
::JSONSchema
::parse_size
($size);
959 if (!defined($size)) {
960 return undef if $noerr;
961 die "invalid size: $size\n";
963 $res->{size
} = $size;
966 $res->{type
} = $class->classify_mountpoint($res->{volume
});
971 sub parse_ct_rootfs
{
972 my ($class, $data, $noerr) = @_;
974 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
976 $res->{mp
} = '/' if defined($res);
981 sub parse_ct_mountpoint
{
982 my ($class, $data, $noerr) = @_;
984 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
987 sub print_ct_mountpoint
{
988 my ($class, $info, $nomp) = @_;
989 my $skip = [ 'type' ];
990 push @$skip, 'mp' if $nomp;
991 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
994 sub print_lxc_network
{
995 my ($class, $net) = @_;
996 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
999 sub parse_lxc_network
{
1000 my ($class, $data) = @_;
1004 return $res if !$data;
1006 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1008 $res->{type
} = 'veth';
1009 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1015 my ($class, $name) = @_;
1017 return defined($confdesc->{$name});
1019 # END JSON config code
1021 sub classify_mountpoint
{
1022 my ($class, $vol) = @_;
1023 if ($vol =~ m!^/!) {
1024 return 'device' if $vol =~ m!^/dev/!;
1030 sub is_volume_in_use
{
1031 my ($class, $config, $volid, $include_snapshots) = @_;
1034 $class->foreach_mountpoint($config, sub {
1035 my ($ms, $mountpoint) = @_;
1037 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1040 my $snapshots = $config->{snapshots
};
1041 if ($include_snapshots && $snapshots) {
1042 foreach my $snap (keys %$snapshots) {
1043 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1050 sub has_dev_console
{
1051 my ($class, $conf) = @_;
1053 return !(defined($conf->{console
}) && !$conf->{console
});
1057 my ($class, $conf) = @_;
1059 return $conf->{tty
} // $confdesc->{tty
}->{default};
1063 my ($class, $conf) = @_;
1065 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1068 sub mountpoint_names
{
1069 my ($class, $reverse) = @_;
1071 my @names = ('rootfs');
1073 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1074 push @names, "mp$i";
1077 return $reverse ?
reverse @names : @names;
1080 sub foreach_mountpoint_full
{
1081 my ($class, $conf, $reverse, $func) = @_;
1083 foreach my $key ($class->mountpoint_names($reverse)) {
1084 my $value = $conf->{$key};
1085 next if !defined($value);
1086 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1087 next if !defined($mountpoint);
1089 &$func($key, $mountpoint);
1093 sub foreach_mountpoint
{
1094 my ($class, $conf, $func) = @_;
1096 $class->foreach_mountpoint_full($conf, 0, $func);
1099 sub foreach_mountpoint_reverse
{
1100 my ($class, $conf, $func) = @_;
1102 $class->foreach_mountpoint_full($conf, 1, $func);
1105 sub get_vm_volumes
{
1106 my ($class, $conf, $excludes) = @_;
1110 $class->foreach_mountpoint($conf, sub {
1111 my ($ms, $mountpoint) = @_;
1113 return if $excludes && $ms eq $excludes;
1115 my $volid = $mountpoint->{volume
};
1116 return if !$volid || $mountpoint->{type
} ne 'volume';
1118 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1121 push @$vollist, $volid;