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 ($class, $mp_key, $mountpoint) = @_;
50 return 1 if $mp_key eq 'rootfs';
52 return 0 if $mountpoint->{type
} ne 'volume';
54 return 1 if $mountpoint->{backup
};
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
63 $class->foreach_mountpoint($conf, sub {
64 my ($ms, $mountpoint) = @_;
66 return if $err; # skip further test
67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
70 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
71 $mountpoint->{volume
},
78 sub __snapshot_save_vmstate
{
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
83 sub __snapshot_check_running
{
84 my ($class, $vmid) = @_;
85 return PVE
::LXC
::check_running
($vmid);
88 sub __snapshot_check_freeze_needed
{
89 my ($class, $vmid, $config, $save_vmstate) = @_;
91 my $ret = $class->__snapshot_check_running($vmid);
95 sub __snapshot_freeze
{
96 my ($class, $vmid, $unfreeze) = @_;
99 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
102 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
103 PVE
::LXC
::sync_container_namespace
($vmid);
107 sub __snapshot_create_vol_snapshot
{
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
110 my $storecfg = PVE
::Storage
::config
();
112 return if $snapname eq 'vzdump' &&
113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
115 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
118 sub __snapshot_delete_remove_drive
{
119 my ($class, $snap, $remove_drive) = @_;
121 if ($remove_drive eq 'vmstate') {
122 die "implement me - saving vmstate\n";
124 my $value = $snap->{$remove_drive};
125 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
126 delete $snap->{$remove_drive};
128 $class->add_unused_volume($snap, $mountpoint->{volume
})
129 if ($mountpoint->{type
} eq 'volume');
133 sub __snapshot_delete_vmstate_file
{
134 my ($class, $snap, $force) = @_;
136 die "implement me - saving vmstate\n";
139 sub __snapshot_delete_vol_snapshot
{
140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
145 my $storecfg = PVE
::Storage
::config
();
146 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
147 push @$unused, $mountpoint->{volume
};
150 sub __snapshot_rollback_vol_possible
{
151 my ($class, $mountpoint, $snapname) = @_;
153 my $storecfg = PVE
::Storage
::config
();
154 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
157 sub __snapshot_rollback_vol_rollback
{
158 my ($class, $mountpoint, $snapname) = @_;
160 my $storecfg = PVE
::Storage
::config
();
161 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
164 sub __snapshot_rollback_vm_stop
{
165 my ($class, $vmid) = @_;
167 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
168 if $class->__snapshot_check_running($vmid);
171 sub __snapshot_rollback_vm_start
{
172 my ($class, $vmid, $vmstate, $forcemachine);
174 die "implement me - save vmstate\n";
177 sub __snapshot_foreach_volume
{
178 my ($class, $conf, $func) = @_;
180 $class->foreach_mountpoint($conf, $func);
183 # END implemented abstract methods from PVE::AbstractConfig
185 # BEGIN JSON config code
187 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
193 format
=> 'pve-lxc-mp-string',
194 format_description
=> 'volume',
195 description
=> 'Volume, device or directory to mount into the container.',
199 format
=> 'disk-size',
200 format_description
=> 'DiskSize',
201 description
=> 'Volume size (read only value).',
206 description
=> 'Explicitly enable or disable ACL support.',
211 description
=> 'Read-only mountpoint',
216 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
221 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
222 type
=> 'string', format
=> $rootfs_desc,
223 description
=> "Use volume as container root.",
227 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
228 description
=> "The name of the snapshot.",
229 type
=> 'string', format
=> 'pve-configid',
237 description
=> "Lock/unlock the VM.",
238 enum
=> [qw(migrate backup snapshot rollback)],
243 description
=> "Specifies whether a VM will be started during system bootup.",
246 startup
=> get_standard_option
('pve-startup-order'),
250 description
=> "Enable/disable Template.",
256 enum
=> ['amd64', 'i386'],
257 description
=> "OS architecture type.",
263 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
264 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.",
269 description
=> "Attach a console device (/dev/console) to the container.",
275 description
=> "Specify the number of tty available to the container",
283 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.",
291 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.",
299 description
=> "Amount of RAM for the VM in MB.",
306 description
=> "Amount of SWAP for the VM in MB.",
312 description
=> "Set a host name for the container.",
313 type
=> 'string', format
=> 'dns-name',
319 description
=> "Container description. Only used on the configuration web interface.",
323 type
=> 'string', format
=> 'dns-name-list',
324 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
328 type
=> 'string', format
=> 'address-list',
329 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.",
331 rootfs
=> get_standard_option
('pve-ct-rootfs'),
334 type
=> 'string', format
=> 'pve-configid',
336 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
340 description
=> "Timestamp for snapshots.",
346 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).",
348 enum
=> ['shell', 'console', 'tty'],
354 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
360 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
365 my $valid_lxc_conf_keys = {
369 'lxc.haltsignal' => 1,
370 'lxc.rebootsignal' => 1,
371 'lxc.stopsignal' => 1,
373 'lxc.network.type' => 1,
374 'lxc.network.flags' => 1,
375 'lxc.network.link' => 1,
376 'lxc.network.mtu' => 1,
377 'lxc.network.name' => 1,
378 'lxc.network.hwaddr' => 1,
379 'lxc.network.ipv4' => 1,
380 'lxc.network.ipv4.gateway' => 1,
381 'lxc.network.ipv6' => 1,
382 'lxc.network.ipv6.gateway' => 1,
383 'lxc.network.script.up' => 1,
384 'lxc.network.script.down' => 1,
386 'lxc.console.logfile' => 1,
389 'lxc.devttydir' => 1,
390 'lxc.hook.autodev' => 1,
394 'lxc.mount.entry' => 1,
395 'lxc.mount.auto' => 1,
396 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
397 'lxc.rootfs.mount' => 1,
398 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
399 ', please use mountpoint options in the "rootfs" key',
403 'lxc.aa_profile' => 1,
404 'lxc.aa_allow_incomplete' => 1,
405 'lxc.se_context' => 1,
408 'lxc.hook.pre-start' => 1,
409 'lxc.hook.pre-mount' => 1,
410 'lxc.hook.mount' => 1,
411 'lxc.hook.start' => 1,
412 'lxc.hook.stop' => 1,
413 'lxc.hook.post-stop' => 1,
414 'lxc.hook.clone' => 1,
415 'lxc.hook.destroy' => 1,
418 'lxc.start.auto' => 1,
419 'lxc.start.delay' => 1,
420 'lxc.start.order' => 1,
422 'lxc.environment' => 1,
425 our $netconf_desc = {
429 description
=> "Network interface type.",
434 format_description
=> 'string',
435 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
436 pattern
=> '[-_.\w\d]+',
440 format_description
=> 'bridge',
441 description
=> 'Bridge to attach the network device to.',
442 pattern
=> '[-_.\w\d]+',
447 format_description
=> "XX:XX:XX:XX:XX:XX",
448 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)',
449 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
454 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
455 minimum
=> 64, # minimum ethernet frame is 64 bytes
460 format
=> 'pve-ipv4-config',
461 format_description
=> 'IPv4Format/CIDR',
462 description
=> 'IPv4 address in CIDR format.',
468 format_description
=> 'GatewayIPv4',
469 description
=> 'Default gateway for IPv4 traffic.',
474 format
=> 'pve-ipv6-config',
475 format_description
=> 'IPv6Format/CIDR',
476 description
=> 'IPv6 address in CIDR format.',
482 format_description
=> 'GatewayIPv6',
483 description
=> 'Default gateway for IPv6 traffic.',
488 description
=> "Controls whether this interface's firewall rules should be used.",
495 description
=> "VLAN tag for this interface.",
500 pattern
=> qr/\d+(?:;\d+)*/,
501 format_description
=> 'vlanid[;vlanid...]',
502 description
=> "VLAN ids to pass through the interface",
507 format_description
=> 'mbps',
508 description
=> "Apply rate limiting to the interface",
512 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
514 my $MAX_LXC_NETWORKS = 10;
515 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
516 $confdesc->{"net$i"} = {
518 type
=> 'string', format
=> $netconf_desc,
519 description
=> "Specifies network interfaces for the container.",
523 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
524 sub verify_lxc_mp_string
{
525 my ($mp, $noerr) = @_;
529 # /. or /.. at the end
530 # ../ at the beginning
532 if($mp =~ m
@/\.\
.?
/@ ||
535 return undef if $noerr;
536 die "$mp contains illegal character sequences\n";
545 description
=> 'Whether to include the mountpoint in backups.',
546 verbose_description
=> 'Whether to include the mountpoint in backups '.
547 '(only used for volume mountpoints).',
552 format
=> 'pve-lxc-mp-string',
553 format_description
=> 'Path',
554 description
=> 'Path to the mountpoint as seen from inside the container.',
555 verbose_description
=> "Path to the mountpoint as seen from inside the container.\n\n".
556 "WARNING: Must not contain any symlinks for security reasons."
559 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
563 type
=> 'string', format
=> 'pve-volume-id',
564 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
567 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
568 $confdesc->{"mp$i"} = {
570 type
=> 'string', format
=> $mp_desc,
571 description
=> "Use volume as container mount point.",
576 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
577 $confdesc->{"unused$i"} = $unuseddesc;
580 sub parse_pct_config
{
581 my ($filename, $raw) = @_;
583 return undef if !defined($raw);
586 digest
=> Digest
::SHA
::sha1_hex
($raw),
590 $filename =~ m
|/lxc/(\d
+).conf
$|
591 || die "got strange filename '$filename'";
599 my @lines = split(/\n/, $raw);
600 foreach my $line (@lines) {
601 next if $line =~ m/^\s*$/;
603 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
605 $conf->{description
} = $descr if $descr;
607 $conf = $res->{snapshots
}->{$section} = {};
611 if ($line =~ m/^\#(.*)\s*$/) {
612 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
616 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
619 my $validity = $valid_lxc_conf_keys->{$key} || 0;
620 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
621 push @{$conf->{lxc
}}, [$key, $value];
622 } elsif (my $errmsg = $validity) {
623 warn "vm $vmid - $key: $errmsg\n";
625 warn "vm $vmid - unable to parse config: $line\n";
627 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
628 $descr .= PVE
::Tools
::decode_text
($2);
629 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
630 $conf->{snapstate
} = $1;
631 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
634 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
635 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
636 $conf->{$key} = $value;
638 warn "vm $vmid - unable to parse config: $line\n";
642 $conf->{description
} = $descr if $descr;
644 delete $res->{snapstate
}; # just to be sure
649 sub write_pct_config
{
650 my ($filename, $conf) = @_;
652 delete $conf->{snapstate
}; # just to be sure
654 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
655 my $used_volids = {};
656 foreach my $vid (@$volidlist) {
657 $used_volids->{$vid} = 1;
660 # remove 'unusedX' settings if the volume is still used
661 foreach my $key (keys %$conf) {
662 my $value = $conf->{$key};
663 if ($key =~ m/^unused/ && $used_volids->{$value}) {
664 delete $conf->{$key};
668 my $generate_raw_config = sub {
673 # add description as comment to top of file
674 my $descr = $conf->{description
} || '';
675 foreach my $cl (split(/\n/, $descr)) {
676 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
679 foreach my $key (sort keys %$conf) {
680 next if $key eq 'digest' || $key eq 'description' ||
681 $key eq 'pending' || $key eq 'snapshots' ||
682 $key eq 'snapname' || $key eq 'lxc';
683 my $value = $conf->{$key};
684 die "detected invalid newline inside property '$key'\n"
686 $raw .= "$key: $value\n";
689 if (my $lxcconf = $conf->{lxc
}) {
690 foreach my $entry (@$lxcconf) {
691 my ($k, $v) = @$entry;
699 my $raw = &$generate_raw_config($conf);
701 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
702 $raw .= "\n[$snapname]\n";
703 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
709 sub update_pct_config
{
710 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
719 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
720 $rootdir = "/proc/$pid/root";
723 my $hotplug_error = sub {
732 if (defined($delete)) {
733 foreach my $opt (@$delete) {
734 if (!exists($conf->{$opt})) {
735 warn "no such option: $opt\n";
739 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
740 die "unable to delete required option '$opt'\n";
741 } elsif ($opt eq 'swap') {
742 delete $conf->{$opt};
743 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
744 "memory.memsw.limit_in_bytes", -1);
745 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
746 delete $conf->{$opt};
747 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
748 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
749 next if $hotplug_error->($opt);
750 delete $conf->{$opt};
751 } elsif ($opt eq 'cpulimit') {
752 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
753 delete $conf->{$opt};
754 } elsif ($opt eq 'cpuunits') {
755 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
756 delete $conf->{$opt};
757 } elsif ($opt =~ m/^net(\d)$/) {
758 delete $conf->{$opt};
761 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
762 } elsif ($opt eq 'protection') {
763 delete $conf->{$opt};
764 } elsif ($opt =~ m/^unused(\d+)$/) {
765 next if $hotplug_error->($opt);
766 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
767 push @deleted_volumes, $conf->{$opt};
768 delete $conf->{$opt};
769 } elsif ($opt =~ m/^mp(\d+)$/) {
770 next if $hotplug_error->($opt);
771 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
772 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
773 delete $conf->{$opt};
774 if ($mp->{type
} eq 'volume') {
775 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
777 } elsif ($opt eq 'unprivileged') {
778 die "unable to delete read-only option: '$opt'\n";
780 die "implement me (delete: $opt)"
782 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
786 # There's no separate swap size to configure, there's memory and "total"
787 # memory (iow. memory+swap). This means we have to change them together.
788 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
789 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
790 if (defined($wanted_memory) || defined($wanted_swap)) {
792 my $old_memory = ($conf->{memory
} || 512);
793 my $old_swap = ($conf->{swap
} || 0);
795 $wanted_memory //= $old_memory;
796 $wanted_swap //= $old_swap;
798 my $total = $wanted_memory + $wanted_swap;
800 my $old_total = $old_memory + $old_swap;
801 if ($total > $old_total) {
802 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
803 "memory.memsw.limit_in_bytes",
804 int($total*1024*1024));
805 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
806 "memory.limit_in_bytes",
807 int($wanted_memory*1024*1024));
809 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
810 "memory.limit_in_bytes",
811 int($wanted_memory*1024*1024));
812 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
813 "memory.memsw.limit_in_bytes",
814 int($total*1024*1024));
817 $conf->{memory
} = $wanted_memory;
818 $conf->{swap
} = $wanted_swap;
820 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
823 my $used_volids = {};
824 my $check_content_type = sub {
826 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
827 my $scfg = PVE
::Storage
::config
();
828 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
829 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
830 if !$storage_config->{content
}->{rootdir
};
833 foreach my $opt (keys %$param) {
834 my $value = $param->{$opt};
835 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
836 if ($opt eq 'hostname') {
837 $conf->{$opt} = $value;
838 } elsif ($opt eq 'onboot') {
839 $conf->{$opt} = $value ?
1 : 0;
840 } elsif ($opt eq 'startup') {
841 $conf->{$opt} = $value;
842 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
843 next if $hotplug_error->($opt);
844 $conf->{$opt} = $value;
845 } elsif ($opt eq 'nameserver') {
846 next if $hotplug_error->($opt);
847 my $list = PVE
::LXC
::verify_nameserver_list
($value);
848 $conf->{$opt} = $list;
849 } elsif ($opt eq 'searchdomain') {
850 next if $hotplug_error->($opt);
851 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
852 $conf->{$opt} = $list;
853 } elsif ($opt eq 'cpulimit') {
854 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
855 $conf->{$opt} = $value;
856 } elsif ($opt eq 'cpuunits') {
857 $conf->{$opt} = $value;
858 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
859 } elsif ($opt eq 'description') {
860 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
861 } elsif ($opt =~ m/^net(\d+)$/) {
863 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
865 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
867 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
869 } elsif ($opt eq 'protection') {
870 $conf->{$opt} = $value ?
1 : 0;
871 } elsif ($opt =~ m/^mp(\d+)$/) {
872 next if $hotplug_error->($opt);
873 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
874 my $old = $conf->{$opt};
875 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
876 if ($mp->{type
} eq 'volume') {
877 &$check_content_type($mp);
878 $used_volids->{$mp->{volume
}} = 1;
880 $conf->{$opt} = $value;
882 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
883 if ($mp->{type
} eq 'volume') {
884 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
888 } elsif ($opt eq 'rootfs') {
889 next if $hotplug_error->($opt);
890 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
891 my $old = $conf->{$opt};
892 $conf->{$opt} = $value;
893 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
894 if ($mp->{type
} eq 'volume') {
895 &$check_content_type($mp);
896 $used_volids->{$mp->{volume
}} = 1;
899 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
900 if ($mp->{type
} eq 'volume') {
901 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
905 } elsif ($opt eq 'unprivileged') {
906 die "unable to modify read-only option: '$opt'\n";
907 } elsif ($opt eq 'ostype') {
908 next if $hotplug_error->($opt);
909 $conf->{$opt} = $value;
911 die "implement me: $opt";
913 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
916 # Apply deletions and creations of new volumes
917 if (@deleted_volumes) {
918 my $storage_cfg = PVE
::Storage
::config
();
919 foreach my $volume (@deleted_volumes) {
920 next if $used_volids->{$volume}; # could have been re-added, too
921 # also check for references in snapshots
922 next if $class->is_volume_in_use($conf, $volume, 1);
923 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
928 my $storage_cfg = PVE
::Storage
::config
();
929 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
932 # This should be the last thing we do here
933 if ($running && scalar(@nohotplug)) {
934 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
939 my ($class, $key, $value) = @_;
941 die "unknown setting '$key'\n" if !$confdesc->{$key};
943 my $type = $confdesc->{$key}->{type
};
945 if (!defined($value)) {
946 die "got undefined value\n";
949 if ($value =~ m/[\n\r]/) {
950 die "property contains a line feed\n";
953 if ($type eq 'boolean') {
954 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
955 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
956 die "type check ('boolean') failed - got '$value'\n";
957 } elsif ($type eq 'integer') {
958 return int($1) if $value =~ m/^(\d+)$/;
959 die "type check ('integer') failed - got '$value'\n";
960 } elsif ($type eq 'number') {
961 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
962 die "type check ('number') failed - got '$value'\n";
963 } elsif ($type eq 'string') {
964 if (my $fmt = $confdesc->{$key}->{format
}) {
965 PVE
::JSONSchema
::check_format
($fmt, $value);
975 # add JSON properties for create and set function
976 sub json_config_properties
{
977 my ($class, $prop) = @_;
979 foreach my $opt (keys %$confdesc) {
980 next if $opt eq 'parent' || $opt eq 'snaptime';
981 next if $prop->{$opt};
982 $prop->{$opt} = $confdesc->{$opt};
988 sub __parse_ct_mountpoint_full
{
989 my ($class, $desc, $data, $noerr) = @_;
994 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
996 return undef if $noerr;
1000 if (defined(my $size = $res->{size
})) {
1001 $size = PVE
::JSONSchema
::parse_size
($size);
1002 if (!defined($size)) {
1003 return undef if $noerr;
1004 die "invalid size: $size\n";
1006 $res->{size
} = $size;
1009 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1014 sub parse_ct_rootfs
{
1015 my ($class, $data, $noerr) = @_;
1017 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1019 $res->{mp
} = '/' if defined($res);
1024 sub parse_ct_mountpoint
{
1025 my ($class, $data, $noerr) = @_;
1027 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1030 sub print_ct_mountpoint
{
1031 my ($class, $info, $nomp) = @_;
1032 my $skip = [ 'type' ];
1033 push @$skip, 'mp' if $nomp;
1034 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1037 sub print_lxc_network
{
1038 my ($class, $net) = @_;
1039 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1042 sub parse_lxc_network
{
1043 my ($class, $data) = @_;
1047 return $res if !$data;
1049 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1051 $res->{type
} = 'veth';
1052 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1058 my ($class, $name) = @_;
1060 return defined($confdesc->{$name});
1062 # END JSON config code
1064 sub classify_mountpoint
{
1065 my ($class, $vol) = @_;
1066 if ($vol =~ m!^/!) {
1067 return 'device' if $vol =~ m!^/dev/!;
1073 sub is_volume_in_use
{
1074 my ($class, $config, $volid, $include_snapshots) = @_;
1077 $class->foreach_mountpoint($config, sub {
1078 my ($ms, $mountpoint) = @_;
1080 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1083 my $snapshots = $config->{snapshots
};
1084 if ($include_snapshots && $snapshots) {
1085 foreach my $snap (keys %$snapshots) {
1086 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1093 sub has_dev_console
{
1094 my ($class, $conf) = @_;
1096 return !(defined($conf->{console
}) && !$conf->{console
});
1100 my ($class, $conf) = @_;
1102 return $conf->{tty
} // $confdesc->{tty
}->{default};
1106 my ($class, $conf) = @_;
1108 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1111 sub mountpoint_names
{
1112 my ($class, $reverse) = @_;
1114 my @names = ('rootfs');
1116 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1117 push @names, "mp$i";
1120 return $reverse ?
reverse @names : @names;
1123 sub foreach_mountpoint_full
{
1124 my ($class, $conf, $reverse, $func) = @_;
1126 foreach my $key ($class->mountpoint_names($reverse)) {
1127 my $value = $conf->{$key};
1128 next if !defined($value);
1129 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1130 next if !defined($mountpoint);
1132 &$func($key, $mountpoint);
1136 sub foreach_mountpoint
{
1137 my ($class, $conf, $func) = @_;
1139 $class->foreach_mountpoint_full($conf, 0, $func);
1142 sub foreach_mountpoint_reverse
{
1143 my ($class, $conf, $func) = @_;
1145 $class->foreach_mountpoint_full($conf, 1, $func);
1148 sub get_vm_volumes
{
1149 my ($class, $conf, $excludes) = @_;
1153 $class->foreach_mountpoint($conf, sub {
1154 my ($ms, $mountpoint) = @_;
1156 return if $excludes && $ms eq $excludes;
1158 my $volid = $mountpoint->{volume
};
1159 return if !$volid || $mountpoint->{type
} ne 'volume';
1161 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1164 push @$vollist, $volid;