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 '(must not contain symlinks).',
556 verbose_description
=> "Path to the mountpoint as seen from inside the container.\n\n".
557 "NOTE: Must not contain any symlinks for security reasons."
560 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
564 type
=> 'string', format
=> 'pve-volume-id',
565 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
568 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
569 $confdesc->{"mp$i"} = {
571 type
=> 'string', format
=> $mp_desc,
572 description
=> "Use volume as container mount point.",
577 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
578 $confdesc->{"unused$i"} = $unuseddesc;
581 sub parse_pct_config
{
582 my ($filename, $raw) = @_;
584 return undef if !defined($raw);
587 digest
=> Digest
::SHA
::sha1_hex
($raw),
591 $filename =~ m
|/lxc/(\d
+).conf
$|
592 || die "got strange filename '$filename'";
600 my @lines = split(/\n/, $raw);
601 foreach my $line (@lines) {
602 next if $line =~ m/^\s*$/;
604 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
606 $conf->{description
} = $descr if $descr;
608 $conf = $res->{snapshots
}->{$section} = {};
612 if ($line =~ m/^\#(.*)\s*$/) {
613 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
617 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
620 my $validity = $valid_lxc_conf_keys->{$key} || 0;
621 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
622 push @{$conf->{lxc
}}, [$key, $value];
623 } elsif (my $errmsg = $validity) {
624 warn "vm $vmid - $key: $errmsg\n";
626 warn "vm $vmid - unable to parse config: $line\n";
628 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
629 $descr .= PVE
::Tools
::decode_text
($2);
630 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
631 $conf->{snapstate
} = $1;
632 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
635 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
636 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
637 $conf->{$key} = $value;
639 warn "vm $vmid - unable to parse config: $line\n";
643 $conf->{description
} = $descr if $descr;
645 delete $res->{snapstate
}; # just to be sure
650 sub write_pct_config
{
651 my ($filename, $conf) = @_;
653 delete $conf->{snapstate
}; # just to be sure
655 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
656 my $used_volids = {};
657 foreach my $vid (@$volidlist) {
658 $used_volids->{$vid} = 1;
661 # remove 'unusedX' settings if the volume is still used
662 foreach my $key (keys %$conf) {
663 my $value = $conf->{$key};
664 if ($key =~ m/^unused/ && $used_volids->{$value}) {
665 delete $conf->{$key};
669 my $generate_raw_config = sub {
674 # add description as comment to top of file
675 my $descr = $conf->{description
} || '';
676 foreach my $cl (split(/\n/, $descr)) {
677 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
680 foreach my $key (sort keys %$conf) {
681 next if $key eq 'digest' || $key eq 'description' ||
682 $key eq 'pending' || $key eq 'snapshots' ||
683 $key eq 'snapname' || $key eq 'lxc';
684 my $value = $conf->{$key};
685 die "detected invalid newline inside property '$key'\n"
687 $raw .= "$key: $value\n";
690 if (my $lxcconf = $conf->{lxc
}) {
691 foreach my $entry (@$lxcconf) {
692 my ($k, $v) = @$entry;
700 my $raw = &$generate_raw_config($conf);
702 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
703 $raw .= "\n[$snapname]\n";
704 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
710 sub update_pct_config
{
711 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
720 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
721 $rootdir = "/proc/$pid/root";
724 my $hotplug_error = sub {
733 if (defined($delete)) {
734 foreach my $opt (@$delete) {
735 if (!exists($conf->{$opt})) {
736 warn "no such option: $opt\n";
740 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
741 die "unable to delete required option '$opt'\n";
742 } elsif ($opt eq 'swap') {
743 delete $conf->{$opt};
744 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
745 "memory.memsw.limit_in_bytes", -1);
746 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
747 delete $conf->{$opt};
748 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
749 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
750 next if $hotplug_error->($opt);
751 delete $conf->{$opt};
752 } elsif ($opt eq 'cpulimit') {
753 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
754 delete $conf->{$opt};
755 } elsif ($opt eq 'cpuunits') {
756 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
757 delete $conf->{$opt};
758 } elsif ($opt =~ m/^net(\d)$/) {
759 delete $conf->{$opt};
762 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
763 } elsif ($opt eq 'protection') {
764 delete $conf->{$opt};
765 } elsif ($opt =~ m/^unused(\d+)$/) {
766 next if $hotplug_error->($opt);
767 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
768 push @deleted_volumes, $conf->{$opt};
769 delete $conf->{$opt};
770 } elsif ($opt =~ m/^mp(\d+)$/) {
771 next if $hotplug_error->($opt);
772 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
773 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
774 delete $conf->{$opt};
775 if ($mp->{type
} eq 'volume') {
776 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
778 } elsif ($opt eq 'unprivileged') {
779 die "unable to delete read-only option: '$opt'\n";
781 die "implement me (delete: $opt)"
783 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
787 # There's no separate swap size to configure, there's memory and "total"
788 # memory (iow. memory+swap). This means we have to change them together.
789 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
790 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
791 if (defined($wanted_memory) || defined($wanted_swap)) {
793 my $old_memory = ($conf->{memory
} || 512);
794 my $old_swap = ($conf->{swap
} || 0);
796 $wanted_memory //= $old_memory;
797 $wanted_swap //= $old_swap;
799 my $total = $wanted_memory + $wanted_swap;
801 my $old_total = $old_memory + $old_swap;
802 if ($total > $old_total) {
803 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
804 "memory.memsw.limit_in_bytes",
805 int($total*1024*1024));
806 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
807 "memory.limit_in_bytes",
808 int($wanted_memory*1024*1024));
810 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
811 "memory.limit_in_bytes",
812 int($wanted_memory*1024*1024));
813 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
814 "memory.memsw.limit_in_bytes",
815 int($total*1024*1024));
818 $conf->{memory
} = $wanted_memory;
819 $conf->{swap
} = $wanted_swap;
821 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
824 my $used_volids = {};
825 my $check_content_type = sub {
827 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
828 my $scfg = PVE
::Storage
::config
();
829 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
830 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
831 if !$storage_config->{content
}->{rootdir
};
834 foreach my $opt (keys %$param) {
835 my $value = $param->{$opt};
836 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
837 if ($opt eq 'hostname') {
838 $conf->{$opt} = $value;
839 } elsif ($opt eq 'onboot') {
840 $conf->{$opt} = $value ?
1 : 0;
841 } elsif ($opt eq 'startup') {
842 $conf->{$opt} = $value;
843 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
844 next if $hotplug_error->($opt);
845 $conf->{$opt} = $value;
846 } elsif ($opt eq 'nameserver') {
847 next if $hotplug_error->($opt);
848 my $list = PVE
::LXC
::verify_nameserver_list
($value);
849 $conf->{$opt} = $list;
850 } elsif ($opt eq 'searchdomain') {
851 next if $hotplug_error->($opt);
852 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
853 $conf->{$opt} = $list;
854 } elsif ($opt eq 'cpulimit') {
855 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
856 $conf->{$opt} = $value;
857 } elsif ($opt eq 'cpuunits') {
858 $conf->{$opt} = $value;
859 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
860 } elsif ($opt eq 'description') {
861 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
862 } elsif ($opt =~ m/^net(\d+)$/) {
864 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
866 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
868 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
870 } elsif ($opt eq 'protection') {
871 $conf->{$opt} = $value ?
1 : 0;
872 } elsif ($opt =~ m/^mp(\d+)$/) {
873 next if $hotplug_error->($opt);
874 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
875 my $old = $conf->{$opt};
876 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
877 if ($mp->{type
} eq 'volume') {
878 &$check_content_type($mp);
879 $used_volids->{$mp->{volume
}} = 1;
881 $conf->{$opt} = $value;
883 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
884 if ($mp->{type
} eq 'volume') {
885 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
889 } elsif ($opt eq 'rootfs') {
890 next if $hotplug_error->($opt);
891 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
892 my $old = $conf->{$opt};
893 $conf->{$opt} = $value;
894 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
895 if ($mp->{type
} eq 'volume') {
896 &$check_content_type($mp);
897 $used_volids->{$mp->{volume
}} = 1;
900 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
901 if ($mp->{type
} eq 'volume') {
902 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
906 } elsif ($opt eq 'unprivileged') {
907 die "unable to modify read-only option: '$opt'\n";
908 } elsif ($opt eq 'ostype') {
909 next if $hotplug_error->($opt);
910 $conf->{$opt} = $value;
912 die "implement me: $opt";
914 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
917 # Apply deletions and creations of new volumes
918 if (@deleted_volumes) {
919 my $storage_cfg = PVE
::Storage
::config
();
920 foreach my $volume (@deleted_volumes) {
921 next if $used_volids->{$volume}; # could have been re-added, too
922 # also check for references in snapshots
923 next if $class->is_volume_in_use($conf, $volume, 1);
924 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
929 my $storage_cfg = PVE
::Storage
::config
();
930 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
933 # This should be the last thing we do here
934 if ($running && scalar(@nohotplug)) {
935 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
940 my ($class, $key, $value) = @_;
942 die "unknown setting '$key'\n" if !$confdesc->{$key};
944 my $type = $confdesc->{$key}->{type
};
946 if (!defined($value)) {
947 die "got undefined value\n";
950 if ($value =~ m/[\n\r]/) {
951 die "property contains a line feed\n";
954 if ($type eq 'boolean') {
955 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
956 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
957 die "type check ('boolean') failed - got '$value'\n";
958 } elsif ($type eq 'integer') {
959 return int($1) if $value =~ m/^(\d+)$/;
960 die "type check ('integer') failed - got '$value'\n";
961 } elsif ($type eq 'number') {
962 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
963 die "type check ('number') failed - got '$value'\n";
964 } elsif ($type eq 'string') {
965 if (my $fmt = $confdesc->{$key}->{format
}) {
966 PVE
::JSONSchema
::check_format
($fmt, $value);
976 # add JSON properties for create and set function
977 sub json_config_properties
{
978 my ($class, $prop) = @_;
980 foreach my $opt (keys %$confdesc) {
981 next if $opt eq 'parent' || $opt eq 'snaptime';
982 next if $prop->{$opt};
983 $prop->{$opt} = $confdesc->{$opt};
989 sub __parse_ct_mountpoint_full
{
990 my ($class, $desc, $data, $noerr) = @_;
995 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
997 return undef if $noerr;
1001 if (defined(my $size = $res->{size
})) {
1002 $size = PVE
::JSONSchema
::parse_size
($size);
1003 if (!defined($size)) {
1004 return undef if $noerr;
1005 die "invalid size: $size\n";
1007 $res->{size
} = $size;
1010 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1015 sub parse_ct_rootfs
{
1016 my ($class, $data, $noerr) = @_;
1018 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1020 $res->{mp
} = '/' if defined($res);
1025 sub parse_ct_mountpoint
{
1026 my ($class, $data, $noerr) = @_;
1028 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1031 sub print_ct_mountpoint
{
1032 my ($class, $info, $nomp) = @_;
1033 my $skip = [ 'type' ];
1034 push @$skip, 'mp' if $nomp;
1035 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1038 sub print_lxc_network
{
1039 my ($class, $net) = @_;
1040 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1043 sub parse_lxc_network
{
1044 my ($class, $data) = @_;
1048 return $res if !$data;
1050 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1052 $res->{type
} = 'veth';
1053 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1059 my ($class, $name) = @_;
1061 return defined($confdesc->{$name});
1063 # END JSON config code
1065 sub classify_mountpoint
{
1066 my ($class, $vol) = @_;
1067 if ($vol =~ m!^/!) {
1068 return 'device' if $vol =~ m!^/dev/!;
1074 sub is_volume_in_use
{
1075 my ($class, $config, $volid, $include_snapshots) = @_;
1078 $class->foreach_mountpoint($config, sub {
1079 my ($ms, $mountpoint) = @_;
1081 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1084 my $snapshots = $config->{snapshots
};
1085 if ($include_snapshots && $snapshots) {
1086 foreach my $snap (keys %$snapshots) {
1087 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1094 sub has_dev_console
{
1095 my ($class, $conf) = @_;
1097 return !(defined($conf->{console
}) && !$conf->{console
});
1101 my ($class, $conf) = @_;
1103 return $conf->{tty
} // $confdesc->{tty
}->{default};
1107 my ($class, $conf) = @_;
1109 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1112 sub mountpoint_names
{
1113 my ($class, $reverse) = @_;
1115 my @names = ('rootfs');
1117 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1118 push @names, "mp$i";
1121 return $reverse ?
reverse @names : @names;
1124 sub foreach_mountpoint_full
{
1125 my ($class, $conf, $reverse, $func) = @_;
1127 foreach my $key ($class->mountpoint_names($reverse)) {
1128 my $value = $conf->{$key};
1129 next if !defined($value);
1130 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1131 next if !defined($mountpoint);
1133 &$func($key, $mountpoint);
1137 sub foreach_mountpoint
{
1138 my ($class, $conf, $func) = @_;
1140 $class->foreach_mountpoint_full($conf, 0, $func);
1143 sub foreach_mountpoint_reverse
{
1144 my ($class, $conf, $func) = @_;
1146 $class->foreach_mountpoint_full($conf, 1, $func);
1149 sub get_vm_volumes
{
1150 my ($class, $conf, $excludes) = @_;
1154 $class->foreach_mountpoint($conf, sub {
1155 my ($ms, $mountpoint) = @_;
1157 return if $excludes && $ms eq $excludes;
1159 my $volid = $mountpoint->{volume
};
1160 return if !$volid || $mountpoint->{type
} ne 'volume';
1162 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1165 push @$vollist, $volid;