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};
127 $class->add_unused_volume($snap, $mountpoint->{volume
});
131 sub __snapshot_delete_vmstate_file
{
132 my ($class, $snap, $force) = @_;
134 die "implement me - saving vmstate\n";
137 sub __snapshot_delete_vol_snapshot
{
138 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
140 my $storecfg = PVE
::Storage
::config
();
141 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
142 push @$unused, $mountpoint->{volume
};
145 sub __snapshot_rollback_vol_possible
{
146 my ($class, $mountpoint, $snapname) = @_;
148 my $storecfg = PVE
::Storage
::config
();
149 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
152 sub __snapshot_rollback_vol_rollback
{
153 my ($class, $mountpoint, $snapname) = @_;
155 my $storecfg = PVE
::Storage
::config
();
156 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
159 sub __snapshot_rollback_vm_stop
{
160 my ($class, $vmid) = @_;
162 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
163 if $class->__snapshot_check_running($vmid);
166 sub __snapshot_rollback_vm_start
{
167 my ($class, $vmid, $vmstate, $forcemachine);
169 die "implement me - save vmstate\n";
172 sub __snapshot_foreach_volume
{
173 my ($class, $conf, $func) = @_;
175 $class->foreach_mountpoint($conf, $func);
178 # END implemented abstract methods from PVE::AbstractConfig
180 # BEGIN JSON config code
182 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
188 format
=> 'pve-lxc-mp-string',
189 format_description
=> 'volume',
190 description
=> 'Volume, device or directory to mount into the container.',
194 format
=> 'disk-size',
195 format_description
=> 'DiskSize',
196 description
=> 'Volume size (read only value).',
201 description
=> 'Explicitly enable or disable ACL support.',
206 description
=> 'Read-only mountpoint (not supported with bind mounts)',
211 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
216 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
217 type
=> 'string', format
=> $rootfs_desc,
218 description
=> "Use volume as container root.",
222 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
223 description
=> "The name of the snapshot.",
224 type
=> 'string', format
=> 'pve-configid',
232 description
=> "Lock/unlock the VM.",
233 enum
=> [qw(migrate backup snapshot rollback)],
238 description
=> "Specifies whether a VM will be started during system bootup.",
241 startup
=> get_standard_option
('pve-startup-order'),
245 description
=> "Enable/disable Template.",
251 enum
=> ['amd64', 'i386'],
252 description
=> "OS architecture type.",
258 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
259 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.",
264 description
=> "Attach a console device (/dev/console) to the container.",
270 description
=> "Specify the number of tty available to the container",
278 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.",
286 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.",
294 description
=> "Amount of RAM for the VM in MB.",
301 description
=> "Amount of SWAP for the VM in MB.",
307 description
=> "Set a host name for the container.",
308 type
=> 'string', format
=> 'dns-name',
314 description
=> "Container description. Only used on the configuration web interface.",
318 type
=> 'string', format
=> 'dns-name-list',
319 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
323 type
=> 'string', format
=> 'address-list',
324 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.",
326 rootfs
=> get_standard_option
('pve-ct-rootfs'),
329 type
=> 'string', format
=> 'pve-configid',
331 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
335 description
=> "Timestamp for snapshots.",
341 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).",
343 enum
=> ['shell', 'console', 'tty'],
349 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
355 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
360 my $valid_lxc_conf_keys = {
364 'lxc.haltsignal' => 1,
365 'lxc.rebootsignal' => 1,
366 'lxc.stopsignal' => 1,
368 'lxc.network.type' => 1,
369 'lxc.network.flags' => 1,
370 'lxc.network.link' => 1,
371 'lxc.network.mtu' => 1,
372 'lxc.network.name' => 1,
373 'lxc.network.hwaddr' => 1,
374 'lxc.network.ipv4' => 1,
375 'lxc.network.ipv4.gateway' => 1,
376 'lxc.network.ipv6' => 1,
377 'lxc.network.ipv6.gateway' => 1,
378 'lxc.network.script.up' => 1,
379 'lxc.network.script.down' => 1,
381 'lxc.console.logfile' => 1,
384 'lxc.devttydir' => 1,
385 'lxc.hook.autodev' => 1,
389 'lxc.mount.entry' => 1,
390 'lxc.mount.auto' => 1,
391 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
392 'lxc.rootfs.mount' => 1,
393 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
394 ', please use mountpoint options in the "rootfs" key',
398 'lxc.aa_profile' => 1,
399 'lxc.aa_allow_incomplete' => 1,
400 'lxc.se_context' => 1,
403 'lxc.hook.pre-start' => 1,
404 'lxc.hook.pre-mount' => 1,
405 'lxc.hook.mount' => 1,
406 'lxc.hook.start' => 1,
407 'lxc.hook.stop' => 1,
408 'lxc.hook.post-stop' => 1,
409 'lxc.hook.clone' => 1,
410 'lxc.hook.destroy' => 1,
413 'lxc.start.auto' => 1,
414 'lxc.start.delay' => 1,
415 'lxc.start.order' => 1,
417 'lxc.environment' => 1,
420 our $netconf_desc = {
424 description
=> "Network interface type.",
429 format_description
=> 'string',
430 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
431 pattern
=> '[-_.\w\d]+',
435 format_description
=> 'bridge',
436 description
=> 'Bridge to attach the network device to.',
437 pattern
=> '[-_.\w\d]+',
442 format_description
=> "XX:XX:XX:XX:XX:XX",
443 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)',
444 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
449 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
450 minimum
=> 64, # minimum ethernet frame is 64 bytes
455 format
=> 'pve-ipv4-config',
456 format_description
=> 'IPv4Format/CIDR',
457 description
=> 'IPv4 address in CIDR format.',
463 format_description
=> 'GatewayIPv4',
464 description
=> 'Default gateway for IPv4 traffic.',
469 format
=> 'pve-ipv6-config',
470 format_description
=> 'IPv6Format/CIDR',
471 description
=> 'IPv6 address in CIDR format.',
477 format_description
=> 'GatewayIPv6',
478 description
=> 'Default gateway for IPv6 traffic.',
483 description
=> "Controls whether this interface's firewall rules should be used.",
490 description
=> "VLAN tag for this interface.",
495 pattern
=> qr/\d+(?:;\d+)*/,
496 format_description
=> 'vlanid[;vlanid...]',
497 description
=> "VLAN ids to pass through the interface",
502 format_description
=> 'mbps',
503 description
=> "Apply rate limiting to the interface",
507 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
509 my $MAX_LXC_NETWORKS = 10;
510 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
511 $confdesc->{"net$i"} = {
513 type
=> 'string', format
=> $netconf_desc,
514 description
=> "Specifies network interfaces for the container.",
518 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
519 sub verify_lxc_mp_string
{
520 my ($mp, $noerr) = @_;
524 # /. or /.. at the end
525 # ../ at the beginning
527 if($mp =~ m
@/\.\
.?
/@ ||
530 return undef if $noerr;
531 die "$mp contains illegal character sequences\n";
540 description
=> 'Whether to include the mountpoint in backups.',
545 format
=> 'pve-lxc-mp-string',
546 format_description
=> 'Path',
547 description
=> 'Path to the mountpoint as seen from inside the container.',
550 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
554 type
=> 'string', format
=> 'pve-volume-id',
555 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
558 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
559 $confdesc->{"mp$i"} = {
561 type
=> 'string', format
=> $mp_desc,
562 description
=> "Use volume as container mount point.",
567 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
568 $confdesc->{"unused$i"} = $unuseddesc;
571 sub parse_pct_config
{
572 my ($filename, $raw) = @_;
574 return undef if !defined($raw);
577 digest
=> Digest
::SHA
::sha1_hex
($raw),
581 $filename =~ m
|/lxc/(\d
+).conf
$|
582 || die "got strange filename '$filename'";
590 my @lines = split(/\n/, $raw);
591 foreach my $line (@lines) {
592 next if $line =~ m/^\s*$/;
594 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
596 $conf->{description
} = $descr if $descr;
598 $conf = $res->{snapshots
}->{$section} = {};
602 if ($line =~ m/^\#(.*)\s*$/) {
603 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
607 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
610 my $validity = $valid_lxc_conf_keys->{$key} || 0;
611 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
612 push @{$conf->{lxc
}}, [$key, $value];
613 } elsif (my $errmsg = $validity) {
614 warn "vm $vmid - $key: $errmsg\n";
616 warn "vm $vmid - unable to parse config: $line\n";
618 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
619 $descr .= PVE
::Tools
::decode_text
($2);
620 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
621 $conf->{snapstate
} = $1;
622 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
625 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
626 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
627 $conf->{$key} = $value;
629 warn "vm $vmid - unable to parse config: $line\n";
633 $conf->{description
} = $descr if $descr;
635 delete $res->{snapstate
}; # just to be sure
640 sub write_pct_config
{
641 my ($filename, $conf) = @_;
643 delete $conf->{snapstate
}; # just to be sure
645 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
646 my $used_volids = {};
647 foreach my $vid (@$volidlist) {
648 $used_volids->{$vid} = 1;
651 # remove 'unusedX' settings if the volume is still used
652 foreach my $key (keys %$conf) {
653 my $value = $conf->{$key};
654 if ($key =~ m/^unused/ && $used_volids->{$value}) {
655 delete $conf->{$key};
659 my $generate_raw_config = sub {
664 # add description as comment to top of file
665 my $descr = $conf->{description
} || '';
666 foreach my $cl (split(/\n/, $descr)) {
667 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
670 foreach my $key (sort keys %$conf) {
671 next if $key eq 'digest' || $key eq 'description' ||
672 $key eq 'pending' || $key eq 'snapshots' ||
673 $key eq 'snapname' || $key eq 'lxc';
674 my $value = $conf->{$key};
675 die "detected invalid newline inside property '$key'\n"
677 $raw .= "$key: $value\n";
680 if (my $lxcconf = $conf->{lxc
}) {
681 foreach my $entry (@$lxcconf) {
682 my ($k, $v) = @$entry;
690 my $raw = &$generate_raw_config($conf);
692 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
693 $raw .= "\n[$snapname]\n";
694 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
700 sub update_pct_config
{
701 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
710 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
711 $rootdir = "/proc/$pid/root";
714 my $hotplug_error = sub {
723 if (defined($delete)) {
724 foreach my $opt (@$delete) {
725 if (!exists($conf->{$opt})) {
726 warn "no such option: $opt\n";
730 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
731 die "unable to delete required option '$opt'\n";
732 } elsif ($opt eq 'swap') {
733 delete $conf->{$opt};
734 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
735 "memory.memsw.limit_in_bytes", -1);
736 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
737 delete $conf->{$opt};
738 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
739 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
740 next if $hotplug_error->($opt);
741 delete $conf->{$opt};
742 } elsif ($opt eq 'cpulimit') {
743 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
744 delete $conf->{$opt};
745 } elsif ($opt eq 'cpuunits') {
746 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits
}->{default});
747 delete $conf->{$opt};
748 } elsif ($opt =~ m/^net(\d)$/) {
749 delete $conf->{$opt};
752 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
753 } elsif ($opt eq 'protection') {
754 delete $conf->{$opt};
755 } elsif ($opt =~ m/^unused(\d+)$/) {
756 next if $hotplug_error->($opt);
757 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
758 push @deleted_volumes, $conf->{$opt};
759 delete $conf->{$opt};
760 } elsif ($opt =~ m/^mp(\d+)$/) {
761 next if $hotplug_error->($opt);
762 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid drive '$opt'");
763 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$opt});
764 delete $conf->{$opt};
765 if ($mp->{type
} eq 'volume') {
766 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
768 } elsif ($opt eq 'unprivileged') {
769 die "unable to delete read-only option: '$opt'\n";
771 die "implement me (delete: $opt)"
773 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
777 # There's no separate swap size to configure, there's memory and "total"
778 # memory (iow. memory+swap). This means we have to change them together.
779 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
780 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
781 if (defined($wanted_memory) || defined($wanted_swap)) {
783 my $old_memory = ($conf->{memory
} || 512);
784 my $old_swap = ($conf->{swap
} || 0);
786 $wanted_memory //= $old_memory;
787 $wanted_swap //= $old_swap;
789 my $total = $wanted_memory + $wanted_swap;
791 my $old_total = $old_memory + $old_swap;
792 if ($total > $old_total) {
793 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
794 "memory.memsw.limit_in_bytes",
795 int($total*1024*1024));
796 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
797 "memory.limit_in_bytes",
798 int($wanted_memory*1024*1024));
800 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
801 "memory.limit_in_bytes",
802 int($wanted_memory*1024*1024));
803 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
804 "memory.memsw.limit_in_bytes",
805 int($total*1024*1024));
808 $conf->{memory
} = $wanted_memory;
809 $conf->{swap
} = $wanted_swap;
811 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
814 my $used_volids = {};
815 my $check_content_type = sub {
817 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
818 my $scfg = PVE
::Storage
::config
();
819 my $storage_config = PVE
::Storage
::storage_config
($scfg, $sid);
820 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
821 if !$storage_config->{content
}->{rootdir
};
824 foreach my $opt (keys %$param) {
825 my $value = $param->{$opt};
826 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
827 if ($opt eq 'hostname') {
828 $conf->{$opt} = $value;
829 } elsif ($opt eq 'onboot') {
830 $conf->{$opt} = $value ?
1 : 0;
831 } elsif ($opt eq 'startup') {
832 $conf->{$opt} = $value;
833 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
834 next if $hotplug_error->($opt);
835 $conf->{$opt} = $value;
836 } elsif ($opt eq 'nameserver') {
837 next if $hotplug_error->($opt);
838 my $list = PVE
::LXC
::verify_nameserver_list
($value);
839 $conf->{$opt} = $list;
840 } elsif ($opt eq 'searchdomain') {
841 next if $hotplug_error->($opt);
842 my $list = PVE
::LXC
::verify_searchdomain_list
($value);
843 $conf->{$opt} = $list;
844 } elsif ($opt eq 'cpulimit') {
845 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
846 $conf->{$opt} = $value;
847 } elsif ($opt eq 'cpuunits') {
848 $conf->{$opt} = $value;
849 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
850 } elsif ($opt eq 'description') {
851 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
852 } elsif ($opt =~ m/^net(\d+)$/) {
854 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
856 $conf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
858 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
860 } elsif ($opt eq 'protection') {
861 $conf->{$opt} = $value ?
1 : 0;
862 } elsif ($opt =~ m/^mp(\d+)$/) {
863 next if $hotplug_error->($opt);
864 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
865 my $old = $conf->{$opt};
866 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
867 if ($mp->{type
} eq 'volume') {
868 &$check_content_type($mp);
869 $used_volids->{$mp->{volume
}} = 1;
871 $conf->{$opt} = $value;
873 my $mp = PVE
::LXC
::Config-
>parse_ct_mountpoint($old);
874 if ($mp->{type
} eq 'volume') {
875 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
879 } elsif ($opt eq 'rootfs') {
880 next if $hotplug_error->($opt);
881 PVE
::LXC
::Config-
>check_protection($conf, $check_protection_msg);
882 my $old = $conf->{$opt};
883 $conf->{$opt} = $value;
884 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($value);
885 if ($mp->{type
} eq 'volume') {
886 &$check_content_type($mp);
887 $used_volids->{$mp->{volume
}} = 1;
890 my $mp = PVE
::LXC
::Config-
>parse_ct_rootfs($old);
891 if ($mp->{type
} eq 'volume') {
892 PVE
::LXC
::Config-
>add_unused_volume($conf, $mp->{volume
});
896 } elsif ($opt eq 'unprivileged') {
897 die "unable to modify read-only option: '$opt'\n";
898 } elsif ($opt eq 'ostype') {
899 next if $hotplug_error->($opt);
900 $conf->{$opt} = $value;
902 die "implement me: $opt";
904 PVE
::LXC
::Config-
>write_config($vmid, $conf) if $running;
907 # Apply deletions and creations of new volumes
908 if (@deleted_volumes) {
909 my $storage_cfg = PVE
::Storage
::config
();
910 foreach my $volume (@deleted_volumes) {
911 next if $used_volids->{$volume}; # could have been re-added, too
912 # also check for references in snapshots
913 next if $class->is_volume_in_use($conf, $volume, 1);
914 PVE
::LXC
::delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
919 my $storage_cfg = PVE
::Storage
::config
();
920 PVE
::LXC
::create_disks
($storage_cfg, $vmid, $conf, $conf);
923 # This should be the last thing we do here
924 if ($running && scalar(@nohotplug)) {
925 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
930 my ($class, $key, $value) = @_;
932 die "unknown setting '$key'\n" if !$confdesc->{$key};
934 my $type = $confdesc->{$key}->{type
};
936 if (!defined($value)) {
937 die "got undefined value\n";
940 if ($value =~ m/[\n\r]/) {
941 die "property contains a line feed\n";
944 if ($type eq 'boolean') {
945 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
946 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
947 die "type check ('boolean') failed - got '$value'\n";
948 } elsif ($type eq 'integer') {
949 return int($1) if $value =~ m/^(\d+)$/;
950 die "type check ('integer') failed - got '$value'\n";
951 } elsif ($type eq 'number') {
952 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
953 die "type check ('number') failed - got '$value'\n";
954 } elsif ($type eq 'string') {
955 if (my $fmt = $confdesc->{$key}->{format
}) {
956 PVE
::JSONSchema
::check_format
($fmt, $value);
966 # add JSON properties for create and set function
967 sub json_config_properties
{
968 my ($class, $prop) = @_;
970 foreach my $opt (keys %$confdesc) {
971 next if $opt eq 'parent' || $opt eq 'snaptime';
972 next if $prop->{$opt};
973 $prop->{$opt} = $confdesc->{$opt};
979 sub __parse_ct_mountpoint_full
{
980 my ($class, $desc, $data, $noerr) = @_;
985 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
987 return undef if $noerr;
991 if (defined(my $size = $res->{size
})) {
992 $size = PVE
::JSONSchema
::parse_size
($size);
993 if (!defined($size)) {
994 return undef if $noerr;
995 die "invalid size: $size\n";
997 $res->{size
} = $size;
1000 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1005 sub parse_ct_rootfs
{
1006 my ($class, $data, $noerr) = @_;
1008 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1010 $res->{mp
} = '/' if defined($res);
1015 sub parse_ct_mountpoint
{
1016 my ($class, $data, $noerr) = @_;
1018 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1021 sub print_ct_mountpoint
{
1022 my ($class, $info, $nomp) = @_;
1023 my $skip = [ 'type' ];
1024 push @$skip, 'mp' if $nomp;
1025 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1028 sub print_lxc_network
{
1029 my ($class, $net) = @_;
1030 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1033 sub parse_lxc_network
{
1034 my ($class, $data) = @_;
1038 return $res if !$data;
1040 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1042 $res->{type
} = 'veth';
1043 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1049 my ($class, $name) = @_;
1051 return defined($confdesc->{$name});
1053 # END JSON config code
1055 sub classify_mountpoint
{
1056 my ($class, $vol) = @_;
1057 if ($vol =~ m!^/!) {
1058 return 'device' if $vol =~ m!^/dev/!;
1064 sub is_volume_in_use
{
1065 my ($class, $config, $volid, $include_snapshots) = @_;
1068 $class->foreach_mountpoint($config, sub {
1069 my ($ms, $mountpoint) = @_;
1071 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1074 my $snapshots = $config->{snapshots
};
1075 if ($include_snapshots && $snapshots) {
1076 foreach my $snap (keys %$snapshots) {
1077 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1084 sub has_dev_console
{
1085 my ($class, $conf) = @_;
1087 return !(defined($conf->{console
}) && !$conf->{console
});
1091 my ($class, $conf) = @_;
1093 return $conf->{tty
} // $confdesc->{tty
}->{default};
1097 my ($class, $conf) = @_;
1099 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1102 sub mountpoint_names
{
1103 my ($class, $reverse) = @_;
1105 my @names = ('rootfs');
1107 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1108 push @names, "mp$i";
1111 return $reverse ?
reverse @names : @names;
1114 sub foreach_mountpoint_full
{
1115 my ($class, $conf, $reverse, $func) = @_;
1117 foreach my $key ($class->mountpoint_names($reverse)) {
1118 my $value = $conf->{$key};
1119 next if !defined($value);
1120 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1121 next if !defined($mountpoint);
1123 &$func($key, $mountpoint);
1127 sub foreach_mountpoint
{
1128 my ($class, $conf, $func) = @_;
1130 $class->foreach_mountpoint_full($conf, 0, $func);
1133 sub foreach_mountpoint_reverse
{
1134 my ($class, $conf, $func) = @_;
1136 $class->foreach_mountpoint_full($conf, 1, $func);
1139 sub get_vm_volumes
{
1140 my ($class, $conf, $excludes) = @_;
1144 $class->foreach_mountpoint($conf, sub {
1145 my ($ms, $mountpoint) = @_;
1147 return if $excludes && $ms eq $excludes;
1149 my $volid = $mountpoint->{volume
};
1150 return if !$volid || $mountpoint->{type
} ne 'volume';
1152 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1155 push @$vollist, $volid;