1 package PVE
::LXC
::Config
;
6 use PVE
::AbstractConfig
;
7 use PVE
::Cluster
qw(cfs_register_file);
9 use PVE
::JSONSchema
qw(get_standard_option);
12 use base
qw(PVE::AbstractConfig);
14 my $nodename = PVE
::INotify
::nodename
();
15 my $lock_handles = {};
16 my $lockdir = "/run/lock/lxc";
18 mkdir "/etc/pve/nodes/$nodename/lxc";
19 my $MAX_MOUNT_POINTS = 10;
20 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
22 # BEGIN implemented abstract methods from PVE::AbstractConfig
28 sub __config_max_unused_disks
{
31 return $MAX_UNUSED_DISKS;
34 sub config_file_lock
{
35 my ($class, $vmid) = @_;
37 return "$lockdir/pve-config-${vmid}.lock";
41 my ($class, $vmid, $node) = @_;
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
47 sub mountpoint_backup_enabled
{
48 my ($mp_key, $mountpoint) = @_;
50 return 1 if $mp_key eq 'rootfs';
52 return 1 if $mountpoint->{backup
};
58 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
61 $class->foreach_mountpoint($conf, sub {
62 my ($ms, $mountpoint) = @_;
64 return if $err; # skip further test
65 return if $backup_only && !mountpoint_backup_enabled
($ms, $mountpoint);
68 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
69 $mountpoint->{volume
},
76 sub __snapshot_save_vmstate
{
77 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
78 die "implement me - snapshot_save_vmstate\n";
81 sub __snapshot_check_running
{
82 my ($class, $vmid) = @_;
83 return PVE
::LXC
::check_running
($vmid);
86 sub __snapshot_check_freeze_needed
{
87 my ($class, $vmid, $config, $save_vmstate) = @_;
89 my $ret = $class->__snapshot_check_running($vmid);
93 sub __snapshot_freeze
{
94 my ($class, $vmid, $unfreeze) = @_;
97 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
100 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
101 PVE
::LXC
::sync_container_namespace
($vmid);
105 sub __snapshot_create_vol_snapshot
{
106 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
108 my $storecfg = PVE
::Storage
::config
();
110 return if $snapname eq 'vzdump' &&
111 !mountpoint_backup_enabled
($ms, $mountpoint);
113 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
116 sub __snapshot_delete_remove_drive
{
117 my ($class, $snap, $remove_drive) = @_;
119 if ($remove_drive eq 'vmstate') {
120 die "implement me - saving vmstate\n";
122 my $value = $snap->{$remove_drive};
123 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
124 delete $snap->{$remove_drive};
125 $class->add_unused_volume($snap, $mountpoint->{volume
});
129 sub __snapshot_delete_vmstate_file
{
130 my ($class, $snap, $force) = @_;
132 die "implement me - saving vmstate\n";
135 sub __snapshot_delete_vol_snapshot
{
136 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
138 my $storecfg = PVE
::Storage
::config
();
139 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
140 push @$unused, $mountpoint->{volume
};
143 sub __snapshot_rollback_vol_possible
{
144 my ($class, $mountpoint, $snapname) = @_;
146 my $storecfg = PVE
::Storage
::config
();
147 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
150 sub __snapshot_rollback_vol_rollback
{
151 my ($class, $mountpoint, $snapname) = @_;
153 my $storecfg = PVE
::Storage
::config
();
154 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
157 sub __snapshot_rollback_vm_stop
{
158 my ($class, $vmid) = @_;
160 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
161 if $class->__snapshot_check_running($vmid);
164 sub __snapshot_rollback_vm_start
{
165 my ($class, $vmid, $vmstate, $forcemachine);
167 die "implement me - save vmstate\n";
170 sub __snapshot_foreach_volume
{
171 my ($class, $conf, $func) = @_;
173 $class->foreach_mountpoint($conf, $func);
176 # END implemented abstract methods from PVE::AbstractConfig
178 # BEGIN JSON config code
180 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
186 format
=> 'pve-lxc-mp-string',
187 format_description
=> 'volume',
188 description
=> 'Volume, device or directory to mount into the container.',
192 description
=> 'Whether to include the mountpoint in backups.',
197 format
=> 'disk-size',
198 format_description
=> 'DiskSize',
199 description
=> 'Volume size (read only value).',
204 description
=> 'Explicitly enable or disable ACL support.',
209 description
=> 'Read-only mountpoint (not supported with bind mounts)',
214 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
219 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
220 type
=> 'string', format
=> $rootfs_desc,
221 description
=> "Use volume as container root.",
225 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
226 description
=> "The name of the snapshot.",
227 type
=> 'string', format
=> 'pve-configid',
235 description
=> "Lock/unlock the VM.",
236 enum
=> [qw(migrate backup snapshot rollback)],
241 description
=> "Specifies whether a VM will be started during system bootup.",
244 startup
=> get_standard_option
('pve-startup-order'),
248 description
=> "Enable/disable Template.",
254 enum
=> ['amd64', 'i386'],
255 description
=> "OS architecture type.",
261 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
262 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.",
267 description
=> "Attach a console device (/dev/console) to the container.",
273 description
=> "Specify the number of tty available to the container",
281 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.",
289 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.",
297 description
=> "Amount of RAM for the VM in MB.",
304 description
=> "Amount of SWAP for the VM in MB.",
310 description
=> "Set a host name for the container.",
311 type
=> 'string', format
=> 'dns-name',
317 description
=> "Container description. Only used on the configuration web interface.",
321 type
=> 'string', format
=> 'dns-name-list',
322 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
326 type
=> 'string', format
=> 'address-list',
327 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.",
329 rootfs
=> get_standard_option
('pve-ct-rootfs'),
332 type
=> 'string', format
=> 'pve-configid',
334 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
338 description
=> "Timestamp for snapshots.",
344 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).",
346 enum
=> ['shell', 'console', 'tty'],
352 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
358 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
363 my $valid_lxc_conf_keys = {
367 'lxc.haltsignal' => 1,
368 'lxc.rebootsignal' => 1,
369 'lxc.stopsignal' => 1,
371 'lxc.network.type' => 1,
372 'lxc.network.flags' => 1,
373 'lxc.network.link' => 1,
374 'lxc.network.mtu' => 1,
375 'lxc.network.name' => 1,
376 'lxc.network.hwaddr' => 1,
377 'lxc.network.ipv4' => 1,
378 'lxc.network.ipv4.gateway' => 1,
379 'lxc.network.ipv6' => 1,
380 'lxc.network.ipv6.gateway' => 1,
381 'lxc.network.script.up' => 1,
382 'lxc.network.script.down' => 1,
384 'lxc.console.logfile' => 1,
387 'lxc.devttydir' => 1,
388 'lxc.hook.autodev' => 1,
392 'lxc.mount.entry' => 1,
393 'lxc.mount.auto' => 1,
394 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
395 'lxc.rootfs.mount' => 1,
396 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
397 ', please use mountpoint options in the "rootfs" key',
401 'lxc.aa_profile' => 1,
402 'lxc.aa_allow_incomplete' => 1,
403 'lxc.se_context' => 1,
406 'lxc.hook.pre-start' => 1,
407 'lxc.hook.pre-mount' => 1,
408 'lxc.hook.mount' => 1,
409 'lxc.hook.start' => 1,
410 'lxc.hook.stop' => 1,
411 'lxc.hook.post-stop' => 1,
412 'lxc.hook.clone' => 1,
413 'lxc.hook.destroy' => 1,
416 'lxc.start.auto' => 1,
417 'lxc.start.delay' => 1,
418 'lxc.start.order' => 1,
420 'lxc.environment' => 1,
423 our $netconf_desc = {
427 description
=> "Network interface type.",
432 format_description
=> 'string',
433 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
434 pattern
=> '[-_.\w\d]+',
438 format_description
=> 'bridge',
439 description
=> 'Bridge to attach the network device to.',
440 pattern
=> '[-_.\w\d]+',
445 format_description
=> "XX:XX:XX:XX:XX:XX",
446 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)',
447 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
452 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
453 minimum
=> 64, # minimum ethernet frame is 64 bytes
458 format
=> 'pve-ipv4-config',
459 format_description
=> 'IPv4Format/CIDR',
460 description
=> 'IPv4 address in CIDR format.',
466 format_description
=> 'GatewayIPv4',
467 description
=> 'Default gateway for IPv4 traffic.',
472 format
=> 'pve-ipv6-config',
473 format_description
=> 'IPv6Format/CIDR',
474 description
=> 'IPv6 address in CIDR format.',
480 format_description
=> 'GatewayIPv6',
481 description
=> 'Default gateway for IPv6 traffic.',
486 description
=> "Controls whether this interface's firewall rules should be used.",
493 description
=> "VLAN tag for this interface.",
498 pattern
=> qr/\d+(?:;\d+)*/,
499 format_description
=> 'vlanid[;vlanid...]',
500 description
=> "VLAN ids to pass through the interface",
505 format_description
=> 'mbps',
506 description
=> "Apply rate limiting to the interface",
510 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
512 my $MAX_LXC_NETWORKS = 10;
513 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
514 $confdesc->{"net$i"} = {
516 type
=> 'string', format
=> $netconf_desc,
517 description
=> "Specifies network interfaces for the container.",
521 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
522 sub verify_lxc_mp_string
{
523 my ($mp, $noerr) = @_;
527 # /. or /.. at the end
528 # ../ at the beginning
530 if($mp =~ m
@/\.\
.?
/@ ||
533 return undef if $noerr;
534 die "$mp contains illegal character sequences\n";
543 format
=> 'pve-lxc-mp-string',
544 format_description
=> 'Path',
545 description
=> 'Path to the mountpoint as seen from inside the container.',
548 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
552 type
=> 'string', format
=> 'pve-volume-id',
553 description
=> "Reference to unused volumes.",
556 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
557 $confdesc->{"mp$i"} = {
559 type
=> 'string', format
=> $mp_desc,
560 description
=> "Use volume as container mount point (experimental feature).",
565 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
566 $confdesc->{"unused$i"} = $unuseddesc;
569 sub parse_pct_config
{
570 my ($filename, $raw) = @_;
572 return undef if !defined($raw);
575 digest
=> Digest
::SHA
::sha1_hex
($raw),
579 $filename =~ m
|/lxc/(\d
+).conf
$|
580 || die "got strange filename '$filename'";
588 my @lines = split(/\n/, $raw);
589 foreach my $line (@lines) {
590 next if $line =~ m/^\s*$/;
592 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
594 $conf->{description
} = $descr if $descr;
596 $conf = $res->{snapshots
}->{$section} = {};
600 if ($line =~ m/^\#(.*)\s*$/) {
601 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
605 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
608 my $validity = $valid_lxc_conf_keys->{$key} || 0;
609 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
610 push @{$conf->{lxc
}}, [$key, $value];
611 } elsif (my $errmsg = $validity) {
612 warn "vm $vmid - $key: $errmsg\n";
614 warn "vm $vmid - unable to parse config: $line\n";
616 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
617 $descr .= PVE
::Tools
::decode_text
($2);
618 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
619 $conf->{snapstate
} = $1;
620 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
623 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
624 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
625 $conf->{$key} = $value;
627 warn "vm $vmid - unable to parse config: $line\n";
631 $conf->{description
} = $descr if $descr;
633 delete $res->{snapstate
}; # just to be sure
638 sub write_pct_config
{
639 my ($filename, $conf) = @_;
641 delete $conf->{snapstate
}; # just to be sure
643 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
644 my $used_volids = {};
645 foreach my $vid (@$volidlist) {
646 $used_volids->{$vid} = 1;
649 # remove 'unusedX' settings if the volume is still used
650 foreach my $key (keys %$conf) {
651 my $value = $conf->{$key};
652 if ($key =~ m/^unused/ && $used_volids->{$value}) {
653 delete $conf->{$key};
657 my $generate_raw_config = sub {
662 # add description as comment to top of file
663 my $descr = $conf->{description
} || '';
664 foreach my $cl (split(/\n/, $descr)) {
665 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
668 foreach my $key (sort keys %$conf) {
669 next if $key eq 'digest' || $key eq 'description' ||
670 $key eq 'pending' || $key eq 'snapshots' ||
671 $key eq 'snapname' || $key eq 'lxc';
672 my $value = $conf->{$key};
673 die "detected invalid newline inside property '$key'\n"
675 $raw .= "$key: $value\n";
678 if (my $lxcconf = $conf->{lxc
}) {
679 foreach my $entry (@$lxcconf) {
680 my ($k, $v) = @$entry;
688 my $raw = &$generate_raw_config($conf);
690 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
691 $raw .= "\n[$snapname]\n";
692 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
698 sub update_pct_config
{
699 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
708 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
709 $rootdir = "/proc/$pid/root";
712 my $hotplug_error = sub {
721 if (defined($delete)) {
722 foreach my $opt (@$delete) {
723 if (!exists($conf->{$opt})) {
724 warn "no such option: $opt\n";
728 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
729 die "unable to delete required option '$opt'\n";
730 } elsif ($opt eq 'swap') {
731 delete $conf->{$opt};
732 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
733 "memory.memsw.limit_in_bytes", -1);
734 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
735 delete $conf->{$opt};
736 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
737 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
738 next if $hotplug_error->($opt);
739 delete $conf->{$opt};
740 } elsif ($opt eq 'cpulimit') {
741 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
742 delete $conf->{$opt};
743 } elsif ($opt eq 'cpuunits') {
744 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
745 delete $conf->{$opt};
746 } elsif ($opt =~ m/^net(\d)$/) {
747 delete $conf->{$opt};
750 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
751 } elsif ($opt eq 'protection') {
752 delete $conf->{$opt};
753 } elsif ($opt =~ m/^unused(\d+)$/) {
754 next if $hotplug_error->($opt);
755 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
756 push @deleted_volumes, $conf->{$opt};
757 delete $conf->{$opt};
758 } elsif ($opt =~ m/^mp(\d+)$/) {
759 next if $hotplug_error->($opt);
760 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
761 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
762 delete $conf->{$opt};
763 if ($mp->{type
} eq 'volume') {
764 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
766 } elsif ($opt eq 'unprivileged') {
767 die "unable to delete read-only option: '$opt'\n";
769 die "implement me (delete: $opt)"
771 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
775 # There's no separate swap size to configure, there's memory and "total"
776 # memory (iow. memory+swap). This means we have to change them together.
777 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
778 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
779 if (defined($wanted_memory) || defined($wanted_swap)) {
781 my $old_memory = ($conf->{memory
} || 512);
782 my $old_swap = ($conf->{swap
} || 0);
784 $wanted_memory //= $old_memory;
785 $wanted_swap //= $old_swap;
787 my $total = $wanted_memory + $wanted_swap;
789 my $old_total = $old_memory + $old_swap;
790 if ($total > $old_total) {
791 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
792 "memory.memsw.limit_in_bytes",
793 int($total*1024*1024));
794 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
795 "memory.limit_in_bytes",
796 int($wanted_memory*1024*1024));
798 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
799 "memory.limit_in_bytes",
800 int($wanted_memory*1024*1024));
801 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
802 "memory.memsw.limit_in_bytes",
803 int($total*1024*1024));
806 $conf->{memory
} = $wanted_memory;
807 $conf->{swap
} = $wanted_swap;
809 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
812 my $used_volids = {};
813 my $check_content_type = sub {
815 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
816 my $scfg = PVE
::Storage
::config
();
817 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
818 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
819 if !$storage_config->{content
}->{rootdir
};
822 foreach my $opt (keys %$param) {
823 my $value = $param->{$opt};
824 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
825 if ($opt eq 'hostname') {
826 $conf->{$opt} = $value;
827 } elsif ($opt eq 'onboot') {
828 $conf->{$opt} = $value ?
1 : 0;
829 } elsif ($opt eq 'startup') {
830 $conf->{$opt} = $value;
831 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
832 next if $hotplug_error->($opt);
833 $conf->{$opt} = $value;
834 } elsif ($opt eq 'nameserver') {
835 next if $hotplug_error->($opt);
836 my $list = PVE
::LXC
::verify_nameserver_list
($value);
837 $conf->{$opt} = $list;
838 } elsif ($opt eq 'searchdomain') {
839 next if $hotplug_error->($opt);
840 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
841 $conf->{$opt} = $list;
842 } elsif ($opt eq 'cpulimit') {
843 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
844 $conf->{$opt} = $value;
845 } elsif ($opt eq 'cpuunits') {
846 $conf->{$opt} = $value;
847 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
848 } elsif ($opt eq 'description') {
849 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
850 } elsif ($opt =~ m/^net(\d+)$/) {
852 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
854 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
856 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
858 } elsif ($opt eq 'protection') {
859 $conf->{$opt} = $value ?
1 : 0;
860 } elsif ($opt =~ m/^mp(\d+)$/) {
861 next if $hotplug_error->($opt);
862 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
863 my $old = $conf->{$opt};
864 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
865 if ($mp->{type
} eq 'volume') {
866 &$check_content_type($mp);
867 $used_volids->{$mp->{volume
}} = 1;
869 $conf->{$opt} = $value;
871 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
872 if ($mp->{type
} eq 'volume') {
873 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
877 } elsif ($opt eq 'rootfs') {
878 next if $hotplug_error->($opt);
879 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
880 my $old = $conf->{$opt};
881 $conf->{$opt} = $value;
882 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
883 if ($mp->{type
} eq 'volume') {
884 &$check_content_type($mp);
885 $used_volids->{$mp->{volume
}} = 1;
888 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
889 if ($mp->{type
} eq 'volume') {
890 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
894 } elsif ($opt eq 'unprivileged') {
895 die "unable to modify read-only option: '$opt'\n";
896 } elsif ($opt eq 'ostype') {
897 next if $hotplug_error->($opt);
898 $conf->{$opt} = $value;
900 die "implement me: $opt";
902 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
905 # Apply deletions and creations of new volumes
906 if (@deleted_volumes) {
907 my $storage_cfg = PVE
::Storage
::config
();
908 foreach my $volume (@deleted_volumes) {
909 next if $used_volids->{$volume}; # could have been re-added, too
910 # also check for references in snapshots
911 next if $class->is_volume_in_use($conf, $volume, 1);
912 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
917 my $storage_cfg = PVE
::Storage
::config
();
918 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
921 # This should be the last thing we do here
922 if ($running && scalar(@nohotplug)) {
923 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
928 my ($class, $key, $value) = @_;
930 die "unknown setting '$key'\n" if !$confdesc->{$key};
932 my $type = $confdesc->{$key}->{type
};
934 if (!defined($value)) {
935 die "got undefined value\n";
938 if ($value =~ m/[\n\r]/) {
939 die "property contains a line feed\n";
942 if ($type eq 'boolean') {
943 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
944 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
945 die "type check ('boolean') failed - got '$value'\n";
946 } elsif ($type eq 'integer') {
947 return int($1) if $value =~ m/^(\d+)$/;
948 die "type check ('integer') failed - got '$value'\n";
949 } elsif ($type eq 'number') {
950 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
951 die "type check ('number') failed - got '$value'\n";
952 } elsif ($type eq 'string') {
953 if (my $fmt = $confdesc->{$key}->{format
}) {
954 PVE
::JSONSchema
::check_format
($fmt, $value);
964 # add JSON properties for create and set function
965 sub json_config_properties
{
966 my ($class, $prop) = @_;
968 foreach my $opt (keys %$confdesc) {
969 next if $opt eq 'parent' || $opt eq 'snaptime';
970 next if $prop->{$opt};
971 $prop->{$opt} = $confdesc->{$opt};
977 sub __parse_ct_mountpoint_full
{
978 my ($class, $desc, $data, $noerr) = @_;
983 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
985 return undef if $noerr;
989 if (defined(my $size = $res->{size
})) {
990 $size = PVE
::JSONSchema
::parse_size
($size);
991 if (!defined($size)) {
992 return undef if $noerr;
993 die "invalid size: $size\n";
995 $res->{size
} = $size;
998 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1003 sub parse_ct_rootfs
{
1004 my ($class, $data, $noerr) = @_;
1006 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1008 $res->{mp
} = '/' if defined($res);
1013 sub parse_ct_mountpoint
{
1014 my ($class, $data, $noerr) = @_;
1016 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1019 sub print_ct_mountpoint
{
1020 my ($class, $info, $nomp) = @_;
1021 my $skip = [ 'type' ];
1022 push @$skip, 'mp' if $nomp;
1023 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1026 sub print_lxc_network
{
1027 my ($class, $net) = @_;
1028 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1031 sub parse_lxc_network
{
1032 my ($class, $data) = @_;
1036 return $res if !$data;
1038 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1040 $res->{type
} = 'veth';
1041 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1047 my ($class, $name) = @_;
1049 return defined($confdesc->{$name});
1051 # END JSON config code
1053 sub classify_mountpoint
{
1054 my ($class, $vol) = @_;
1055 if ($vol =~ m!^/!) {
1056 return 'device' if $vol =~ m!^/dev/!;
1062 sub is_volume_in_use
{
1063 my ($class, $config, $volid, $include_snapshots) = @_;
1066 $class->foreach_mountpoint($config, sub {
1067 my ($ms, $mountpoint) = @_;
1069 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1072 my $snapshots = $config->{snapshots
};
1073 if ($include_snapshots && $snapshots) {
1074 foreach my $snap (keys %$snapshots) {
1075 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1082 sub has_dev_console
{
1083 my ($class, $conf) = @_;
1085 return !(defined($conf->{console
}) && !$conf->{console
});
1089 my ($class, $conf) = @_;
1091 return $conf->{tty
} // $confdesc->{tty
}->{default};
1095 my ($class, $conf) = @_;
1097 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1100 sub mountpoint_names
{
1101 my ($class, $reverse) = @_;
1103 my @names = ('rootfs');
1105 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1106 push @names, "mp$i";
1109 return $reverse ?
reverse @names : @names;
1112 sub foreach_mountpoint_full
{
1113 my ($class, $conf, $reverse, $func) = @_;
1115 foreach my $key ($class->mountpoint_names($reverse)) {
1116 my $value = $conf->{$key};
1117 next if !defined($value);
1118 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1119 next if !defined($mountpoint);
1121 &$func($key, $mountpoint);
1125 sub foreach_mountpoint
{
1126 my ($class, $conf, $func) = @_;
1128 $class->foreach_mountpoint_full($conf, 0, $func);
1131 sub foreach_mountpoint_reverse
{
1132 my ($class, $conf, $func) = @_;
1134 $class->foreach_mountpoint_full($conf, 1, $func);
1137 sub get_vm_volumes
{
1138 my ($class, $conf, $excludes) = @_;
1142 $class->foreach_mountpoint($conf, sub {
1143 my ($ms, $mountpoint) = @_;
1145 return if $excludes && $ms eq $excludes;
1147 my $volid = $mountpoint->{volume
};
1148 return if !$volid || $mountpoint->{type
} ne 'volume';
1150 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1153 push @$vollist, $volid;