1 package PVE
::LXC
::Config
;
6 use PVE
::AbstractConfig
;
7 use PVE
::Cluster
qw(cfs_register_file);
8 use PVE
::DataCenterConfig
;
11 use PVE
::JSONSchema
qw(get_standard_option);
14 use base
qw(PVE::AbstractConfig);
16 my $nodename = PVE
::INotify
::nodename
();
17 my $lock_handles = {};
18 my $lockdir = "/run/lock/lxc";
20 mkdir "/etc/pve/nodes/$nodename/lxc";
21 my $MAX_MOUNT_POINTS = 256;
22 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
24 # BEGIN implemented abstract methods from PVE::AbstractConfig
30 sub __config_max_unused_disks
{
33 return $MAX_UNUSED_DISKS;
36 sub config_file_lock
{
37 my ($class, $vmid) = @_;
39 return "$lockdir/pve-config-${vmid}.lock";
43 my ($class, $vmid, $node) = @_;
45 $node = $nodename if !$node;
46 return "nodes/$node/lxc/$vmid.conf";
49 sub mountpoint_backup_enabled
{
50 my ($class, $mp_key, $mountpoint) = @_;
52 return 1 if $mp_key eq 'rootfs';
54 return 0 if $mountpoint->{type
} ne 'volume';
56 return 1 if $mountpoint->{backup
};
62 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
65 $class->foreach_mountpoint($conf, sub {
66 my ($ms, $mountpoint) = @_;
68 return if $err; # skip further test
69 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
72 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature,
73 $mountpoint->{volume
},
80 sub __snapshot_save_vmstate
{
81 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
82 die "implement me - snapshot_save_vmstate\n";
85 sub __snapshot_check_running
{
86 my ($class, $vmid) = @_;
87 return PVE
::LXC
::check_running
($vmid);
90 sub __snapshot_check_freeze_needed
{
91 my ($class, $vmid, $config, $save_vmstate) = @_;
93 my $ret = $class->__snapshot_check_running($vmid);
97 sub __snapshot_freeze
{
98 my ($class, $vmid, $unfreeze) = @_;
101 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
104 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
105 PVE
::LXC
::sync_container_namespace
($vmid);
109 sub __snapshot_create_vol_snapshot
{
110 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
112 my $storecfg = PVE
::Storage
::config
();
114 return if $snapname eq 'vzdump' &&
115 !$class->mountpoint_backup_enabled($ms, $mountpoint);
117 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
120 sub __snapshot_delete_remove_drive
{
121 my ($class, $snap, $remove_drive) = @_;
123 if ($remove_drive eq 'vmstate') {
124 die "implement me - saving vmstate\n";
126 my $value = $snap->{$remove_drive};
127 my $mountpoint = $remove_drive eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
128 delete $snap->{$remove_drive};
130 $class->add_unused_volume($snap, $mountpoint->{volume
})
131 if ($mountpoint->{type
} eq 'volume');
135 sub __snapshot_delete_vmstate_file
{
136 my ($class, $snap, $force) = @_;
138 die "implement me - saving vmstate\n";
141 sub __snapshot_delete_vol_snapshot
{
142 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
144 return if $snapname eq 'vzdump' &&
145 !$class->mountpoint_backup_enabled($ms, $mountpoint);
147 my $storecfg = PVE
::Storage
::config
();
148 PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname);
149 push @$unused, $mountpoint->{volume
};
152 sub __snapshot_rollback_vol_possible
{
153 my ($class, $mountpoint, $snapname) = @_;
155 my $storecfg = PVE
::Storage
::config
();
156 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
159 sub __snapshot_rollback_vol_rollback
{
160 my ($class, $mountpoint, $snapname) = @_;
162 my $storecfg = PVE
::Storage
::config
();
163 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
166 sub __snapshot_rollback_vm_stop
{
167 my ($class, $vmid) = @_;
169 PVE
::LXC
::vm_stop
($vmid, 1)
170 if $class->__snapshot_check_running($vmid);
173 sub __snapshot_rollback_vm_start
{
174 my ($class, $vmid, $vmstate, $data);
176 die "implement me - save vmstate\n";
179 sub __snapshot_rollback_get_unused
{
180 my ($class, $conf, $snap) = @_;
184 $class->__snapshot_foreach_volume($conf, sub {
185 my ($vs, $volume) = @_;
187 return if $volume->{type
} ne 'volume';
190 my $volid = $volume->{volume
};
192 $class->__snapshot_foreach_volume($snap, sub {
193 my ($ms, $mountpoint) = @_;
196 return if ($mountpoint->{type
} ne 'volume');
199 if ($mountpoint->{volume
} && $mountpoint->{volume
} eq $volid);
202 push @$unused, $volid if !$found;
208 sub __snapshot_foreach_volume
{
209 my ($class, $conf, $func) = @_;
211 $class->foreach_mountpoint($conf, $func);
214 # END implemented abstract methods from PVE::AbstractConfig
216 # BEGIN JSON config code
218 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
221 my $valid_mount_option_re = qr/(noatime|nodev|nosuid|noexec)/;
223 sub is_valid_mount_option
{
225 return $option =~ $valid_mount_option_re;
232 format
=> 'pve-lxc-mp-string',
233 format_description
=> 'volume',
234 description
=> 'Volume, device or directory to mount into the container.',
238 format
=> 'disk-size',
239 format_description
=> 'DiskSize',
240 description
=> 'Volume size (read only value).',
245 description
=> 'Explicitly enable or disable ACL support.',
251 description
=> 'Extra mount options for rootfs/mps.',
252 format_description
=> 'opt[;opt...]',
253 pattern
=> qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
257 description
=> 'Read-only mount point',
262 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
267 description
=> 'Will include this volume to a storage replica job.',
273 description
=> 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
274 verbose_description
=> "Mark this non-volume mount point as available on all nodes.\n\nWARNING: This option does not share the mount point automatically, it assumes it is shared already!",
280 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
281 type
=> 'string', format
=> $rootfs_desc,
282 description
=> "Use volume as container root.",
286 my $features_desc = {
290 description
=> "Allow mounting file systems of specific types."
291 ." This should be a list of file system types as used with the mount command."
292 ." Note that this can have negative effects on the container's security."
293 ." With access to a loop device, mounting a file can circumvent the mknod"
294 ." permission of the devices cgroup, mounting an NFS file system can"
295 ." block the host's I/O completely and prevent it from rebooting, etc.",
296 format_description
=> 'fstype;fstype;...',
297 pattern
=> qr/[a-zA-Z0-9_; ]+/,
303 description
=> "Allow nesting."
304 ." Best used with unprivileged containers with additional id mapping."
305 ." Note that this will expose procfs and sysfs contents of the host"
312 description
=> "For unprivileged containers only: Allow the use of the keyctl() system call."
313 ." This is required to use docker inside a container."
314 ." By default unprivileged containers will see this system call as non-existent."
315 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
316 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
317 ." Essentially, you can choose between running systemd-networkd or docker.",
323 description
=> "Allow using 'fuse' file systems in a container."
324 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
332 description
=> "Lock/unlock the VM.",
333 enum
=> [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
338 description
=> "Specifies whether a VM will be started during system bootup.",
341 startup
=> get_standard_option
('pve-startup-order'),
345 description
=> "Enable/disable Template.",
351 enum
=> ['amd64', 'i386', 'arm64', 'armhf'],
352 description
=> "OS architecture type.",
358 enum
=> [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
359 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.",
364 description
=> "Attach a console device (/dev/console) to the container.",
370 description
=> "Specify the number of tty available to the container",
378 description
=> "The number of cores assigned to the container. A container can use all available cores by default.",
385 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.",
393 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.",
401 description
=> "Amount of RAM for the VM in MB.",
408 description
=> "Amount of SWAP for the VM in MB.",
414 description
=> "Set a host name for the container.",
415 type
=> 'string', format
=> 'dns-name',
421 description
=> "Container description. Only used on the configuration web interface.",
425 type
=> 'string', format
=> 'dns-name-list',
426 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
430 type
=> 'string', format
=> 'address-list',
431 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.",
433 rootfs
=> get_standard_option
('pve-ct-rootfs'),
436 type
=> 'string', format
=> 'pve-configid',
438 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
442 description
=> "Timestamp for snapshots.",
448 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).",
450 enum
=> ['shell', 'console', 'tty'],
456 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
462 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
468 format
=> $features_desc,
469 description
=> "Allow containers access to advanced features.",
474 format
=> 'pve-volume-id',
475 description
=> 'Script that will be exectued during various steps in the containers lifetime.',
479 my $valid_lxc_conf_keys = {
480 'lxc.apparmor.profile' => 1,
481 'lxc.apparmor.allow_incomplete' => 1,
482 'lxc.apparmor.allow_nesting' => 1,
483 'lxc.apparmor.raw' => 1,
484 'lxc.selinux.context' => 1,
488 'lxc.signal.halt' => 1,
489 'lxc.signal.reboot' => 1,
490 'lxc.signal.stop' => 1,
493 'lxc.console.logfile' => 1,
494 'lxc.console.path' => 1,
496 'lxc.devtty.dir' => 1,
497 'lxc.hook.autodev' => 1,
500 'lxc.mount.fstab' => 1,
501 'lxc.mount.entry' => 1,
502 'lxc.mount.auto' => 1,
503 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
504 'lxc.rootfs.mount' => 1,
505 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
506 ', please use mount point options in the "rootfs" key',
512 'lxc.seccomp.profile' => 1,
513 'lxc.seccomp.notify.proxy' => 1,
514 'lxc.seccomp.notify.cookie' => 1,
516 'lxc.hook.pre-start' => 1,
517 'lxc.hook.pre-mount' => 1,
518 'lxc.hook.mount' => 1,
519 'lxc.hook.start' => 1,
520 'lxc.hook.stop' => 1,
521 'lxc.hook.post-stop' => 1,
522 'lxc.hook.clone' => 1,
523 'lxc.hook.destroy' => 1,
524 'lxc.hook.version' => 1,
525 'lxc.log.level' => 1,
527 'lxc.start.auto' => 1,
528 'lxc.start.delay' => 1,
529 'lxc.start.order' => 1,
531 'lxc.environment' => 1,
533 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
534 'lxc.sysctl.fs.mqueue' => 1,
535 'lxc.sysctl.kernel.msgmax' => 1,
536 'lxc.sysctl.kernel.msgmnb' => 1,
537 'lxc.sysctl.kernel.msgmni' => 1,
538 'lxc.sysctl.kernel.sem' => 1,
539 'lxc.sysctl.kernel.shmall' => 1,
540 'lxc.sysctl.kernel.shmmax' => 1,
541 'lxc.sysctl.kernel.shmmni' => 1,
542 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
545 my $deprecated_lxc_conf_keys = {
546 # Deprecated (removed with lxc 3.0):
547 'lxc.aa_profile' => 'lxc.apparmor.profile',
548 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
549 'lxc.console' => 'lxc.console.path',
550 'lxc.devttydir' => 'lxc.tty.dir',
551 'lxc.haltsignal' => 'lxc.signal.halt',
552 'lxc.rebootsignal' => 'lxc.signal.reboot',
553 'lxc.stopsignal' => 'lxc.signal.stop',
554 'lxc.id_map' => 'lxc.idmap',
555 'lxc.init_cmd' => 'lxc.init.cmd',
556 'lxc.loglevel' => 'lxc.log.level',
557 'lxc.logfile' => 'lxc.log.file',
558 'lxc.mount' => 'lxc.mount.fstab',
559 'lxc.network.type' => 'lxc.net.INDEX.type',
560 'lxc.network.flags' => 'lxc.net.INDEX.flags',
561 'lxc.network.link' => 'lxc.net.INDEX.link',
562 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
563 'lxc.network.name' => 'lxc.net.INDEX.name',
564 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
565 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
566 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
567 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
568 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
569 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
570 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
571 'lxc.pts' => 'lxc.pty.max',
572 'lxc.se_context' => 'lxc.selinux.context',
573 'lxc.seccomp' => 'lxc.seccomp.profile',
574 'lxc.tty' => 'lxc.tty.max',
575 'lxc.utsname' => 'lxc.uts.name',
578 sub is_valid_lxc_conf_key
{
579 my ($vmid, $key) = @_;
580 if ($key =~ /^lxc\.limit\./) {
581 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
584 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
585 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
588 my $validity = $valid_lxc_conf_keys->{$key};
589 return $validity if defined($validity);
590 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
591 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
592 || $key =~ /^lxc\.net\./; # allow custom network definitions
596 our $netconf_desc = {
600 description
=> "Network interface type.",
605 format_description
=> 'string',
606 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
607 pattern
=> '[-_.\w\d]+',
611 format_description
=> 'bridge',
612 description
=> 'Bridge to attach the network device to.',
613 pattern
=> '[-_.\w\d]+',
616 hwaddr
=> get_standard_option
('mac-addr', {
617 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)',
621 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
622 minimum
=> 64, # minimum ethernet frame is 64 bytes
627 format
=> 'pve-ipv4-config',
628 format_description
=> '(IPv4/CIDR|dhcp|manual)',
629 description
=> 'IPv4 address in CIDR format.',
635 format_description
=> 'GatewayIPv4',
636 description
=> 'Default gateway for IPv4 traffic.',
641 format
=> 'pve-ipv6-config',
642 format_description
=> '(IPv6/CIDR|auto|dhcp|manual)',
643 description
=> 'IPv6 address in CIDR format.',
649 format_description
=> 'GatewayIPv6',
650 description
=> 'Default gateway for IPv6 traffic.',
655 description
=> "Controls whether this interface's firewall rules should be used.",
662 description
=> "VLAN tag for this interface.",
667 pattern
=> qr/\d+(?:;\d+)*/,
668 format_description
=> 'vlanid[;vlanid...]',
669 description
=> "VLAN ids to pass through the interface",
674 format_description
=> 'mbps',
675 description
=> "Apply rate limiting to the interface",
679 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
681 my $MAX_LXC_NETWORKS = 10;
682 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
683 $confdesc->{"net$i"} = {
685 type
=> 'string', format
=> $netconf_desc,
686 description
=> "Specifies network interfaces for the container.",
690 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
691 sub verify_lxc_mp_string
{
692 my ($mp, $noerr) = @_;
696 # /. or /.. at the end
697 # ../ at the beginning
699 if($mp =~ m
@/\.\
.?
/@ ||
702 return undef if $noerr;
703 die "$mp contains illegal character sequences\n";
712 description
=> 'Whether to include the mount point in backups.',
713 verbose_description
=> 'Whether to include the mount point in backups '.
714 '(only used for volume mount points).',
719 format
=> 'pve-lxc-mp-string',
720 format_description
=> 'Path',
721 description
=> 'Path to the mount point as seen from inside the container '.
722 '(must not contain symlinks).',
723 verbose_description
=> "Path to the mount point as seen from inside the container.\n\n".
724 "NOTE: Must not contain any symlinks for security reasons."
727 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
731 type
=> 'string', format
=> 'pve-volume-id',
732 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
735 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
736 $confdesc->{"mp$i"} = {
738 type
=> 'string', format
=> $mp_desc,
739 description
=> "Use volume as container mount point.",
744 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
745 $confdesc->{"unused$i"} = $unuseddesc;
748 sub parse_pct_config
{
749 my ($filename, $raw) = @_;
751 return undef if !defined($raw);
754 digest
=> Digest
::SHA
::sha1_hex
($raw),
759 $filename =~ m
|/lxc/(\d
+).conf
$|
760 || die "got strange filename '$filename'";
768 my @lines = split(/\n/, $raw);
769 foreach my $line (@lines) {
770 next if $line =~ m/^\s*$/;
772 if ($line =~ m/^\[pve:pending\]\s*$/i) {
773 $section = 'pending';
774 $conf->{description
} = $descr if $descr;
776 $conf = $res->{$section} = {};
778 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
780 $conf->{description
} = $descr if $descr;
782 $conf = $res->{snapshots
}->{$section} = {};
786 if ($line =~ m/^\#(.*)\s*$/) {
787 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
791 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
794 my $validity = is_valid_lxc_conf_key
($vmid, $key);
795 if ($validity eq 1) {
796 push @{$conf->{lxc
}}, [$key, $value];
797 } elsif (my $errmsg = $validity) {
798 warn "vm $vmid - $key: $errmsg\n";
800 warn "vm $vmid - unable to parse config: $line\n";
802 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
803 $descr .= PVE
::Tools
::decode_text
($2);
804 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
805 $conf->{snapstate
} = $1;
806 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
808 if ($section eq 'pending') {
809 $conf->{delete} = $value;
811 warn "vm $vmid - property 'delete' is only allowed in [pve:pending]\n";
813 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
816 eval { $value = PVE
::LXC
::Config-
>check_type($key, $value); };
817 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
818 $conf->{$key} = $value;
820 warn "vm $vmid - unable to parse config: $line\n";
824 $conf->{description
} = $descr if $descr;
826 delete $res->{snapstate
}; # just to be sure
831 sub write_pct_config
{
832 my ($filename, $conf) = @_;
834 delete $conf->{snapstate
}; # just to be sure
836 my $volidlist = PVE
::LXC
::Config-
>get_vm_volumes($conf);
837 my $used_volids = {};
838 foreach my $vid (@$volidlist) {
839 $used_volids->{$vid} = 1;
842 # remove 'unusedX' settings if the volume is still used
843 foreach my $key (keys %$conf) {
844 my $value = $conf->{$key};
845 if ($key =~ m/^unused/ && $used_volids->{$value}) {
846 delete $conf->{$key};
850 my $generate_raw_config = sub {
855 # add description as comment to top of file
856 my $descr = $conf->{description
} || '';
857 foreach my $cl (split(/\n/, $descr)) {
858 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
861 foreach my $key (sort keys %$conf) {
862 next if $key eq 'digest' || $key eq 'description' ||
863 $key eq 'pending' || $key eq 'snapshots' ||
864 $key eq 'snapname' || $key eq 'lxc';
865 my $value = $conf->{$key};
866 die "detected invalid newline inside property '$key'\n"
868 $raw .= "$key: $value\n";
871 if (my $lxcconf = $conf->{lxc
}) {
872 foreach my $entry (@$lxcconf) {
873 my ($k, $v) = @$entry;
881 my $raw = &$generate_raw_config($conf);
883 if (scalar(keys %{$conf->{pending
}})){
884 $raw .= "\n[pve:pending]\n";
885 $raw .= &$generate_raw_config($conf->{pending
});
888 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
889 $raw .= "\n[$snapname]\n";
890 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
896 sub update_pct_config
{
897 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
899 my $storage_cfg = PVE
::Storage
::config
();
901 foreach my $opt (@$revert) {
902 delete $conf->{pending
}->{$opt};
903 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
906 # write updates to pending section
907 my $modified = {}; # record modified options
909 foreach my $opt (@$delete) {
910 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
911 warn "cannot delete '$opt' - not set in current configuration!\n";
914 $modified->{$opt} = 1;
915 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
916 die "unable to delete required option '$opt'\n";
917 } elsif ($opt =~ m/^unused(\d+)$/) {
918 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
919 } elsif ($opt =~ m/^mp(\d+)$/) {
920 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
921 } elsif ($opt eq 'unprivileged') {
922 die "unable to delete read-only option: '$opt'\n";
924 $class->add_to_pending_delete($conf, $opt);
927 my $check_content_type = sub {
929 my $sid = PVE
::Storage
::parse_volume_id
($mp->{volume
});
930 my $storage_config = PVE
::Storage
::storage_config
($storage_cfg, $sid);
931 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
932 if !$storage_config->{content
}->{rootdir
};
935 foreach my $opt (sort keys %$param) { # add/change
936 $modified->{$opt} = 1;
937 my $value = $param->{$opt};
938 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
939 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
940 my $mp = $opt eq 'rootfs' ?
$class->parse_ct_rootfs($value) : $class->parse_ct_mountpoint($value);
941 $check_content_type->($mp) if ($mp->{type
} eq 'volume');
942 } elsif ($opt eq 'hookscript') {
943 PVE
::GuestHelpers
::check_hookscript
($value);
944 } elsif ($opt eq 'nameserver') {
945 $value = PVE
::LXC
::verify_nameserver_list
($value);
946 } elsif ($opt eq 'searchdomain') {
947 $value = PVE
::LXC
::verify_searchdomain_list
($value);
948 } elsif ($opt eq 'unprivileged') {
949 die "unable to modify read-only option: '$opt'\n";
951 $conf->{pending
}->{$opt} = $value;
952 $class->remove_from_pending_delete($conf, $opt);
955 my $changes = $class->cleanup_pending($conf);
959 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
961 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
968 my ($class, $key, $value) = @_;
970 die "unknown setting '$key'\n" if !$confdesc->{$key};
972 my $type = $confdesc->{$key}->{type
};
974 if (!defined($value)) {
975 die "got undefined value\n";
978 if ($value =~ m/[\n\r]/) {
979 die "property contains a line feed\n";
982 if ($type eq 'boolean') {
983 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
984 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
985 die "type check ('boolean') failed - got '$value'\n";
986 } elsif ($type eq 'integer') {
987 return int($1) if $value =~ m/^(\d+)$/;
988 die "type check ('integer') failed - got '$value'\n";
989 } elsif ($type eq 'number') {
990 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
991 die "type check ('number') failed - got '$value'\n";
992 } elsif ($type eq 'string') {
993 if (my $fmt = $confdesc->{$key}->{format
}) {
994 PVE
::JSONSchema
::check_format
($fmt, $value);
1004 # add JSON properties for create and set function
1005 sub json_config_properties
{
1006 my ($class, $prop) = @_;
1008 foreach my $opt (keys %$confdesc) {
1009 next if $opt eq 'parent' || $opt eq 'snaptime';
1010 next if $prop->{$opt};
1011 $prop->{$opt} = $confdesc->{$opt};
1017 sub __parse_ct_mountpoint_full
{
1018 my ($class, $desc, $data, $noerr) = @_;
1023 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
1025 return undef if $noerr;
1029 if (defined(my $size = $res->{size
})) {
1030 $size = PVE
::JSONSchema
::parse_size
($size);
1031 if (!defined($size)) {
1032 return undef if $noerr;
1033 die "invalid size: $size\n";
1035 $res->{size
} = $size;
1038 $res->{type
} = $class->classify_mountpoint($res->{volume
});
1043 sub parse_ct_rootfs
{
1044 my ($class, $data, $noerr) = @_;
1046 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1048 $res->{mp
} = '/' if defined($res);
1053 sub parse_ct_mountpoint
{
1054 my ($class, $data, $noerr) = @_;
1056 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1059 sub print_ct_mountpoint
{
1060 my ($class, $info, $nomp) = @_;
1061 my $skip = [ 'type' ];
1062 push @$skip, 'mp' if $nomp;
1063 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
1066 sub print_lxc_network
{
1067 my ($class, $net) = @_;
1068 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
1071 sub parse_lxc_network
{
1072 my ($class, $data) = @_;
1076 return $res if !$data;
1078 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
1080 $res->{type
} = 'veth';
1081 if (!$res->{hwaddr
}) {
1082 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1083 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1089 sub parse_features
{
1090 my ($class, $data) = @_;
1091 return {} if !$data;
1092 return PVE
::JSONSchema
::parse_property_string
($features_desc, $data);
1096 my ($class, $name) = @_;
1098 return defined($confdesc->{$name});
1100 # END JSON config code
1102 my $LXC_FASTPLUG_OPTIONS= {
1114 sub vmconfig_hotplug_pending
{
1115 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1117 my $pid = PVE
::LXC
::find_lxc_pid
($vmid);
1118 my $rootdir = "/proc/$pid/root";
1120 my $add_hotplug_error = sub {
1121 my ($opt, $msg) = @_;
1122 $errors->{$opt} = "unable to hotplug $opt: $msg";
1126 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1127 next if $selection && !$selection->{$opt};
1128 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1129 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1135 $class->write_config($vmid, $conf);
1138 # There's no separate swap size to configure, there's memory and "total"
1139 # memory (iow. memory+swap). This means we have to change them together.
1140 my $hotplug_memory_done;
1141 my $hotplug_memory = sub {
1142 my ($wanted_memory, $wanted_swap) = @_;
1143 my $old_memory = ($conf->{memory
} || $confdesc->{memory
}->{default});
1144 my $old_swap = ($conf->{swap
} || $confdesc->{swap
}->{default});
1146 $wanted_memory //= $old_memory;
1147 $wanted_swap //= $old_swap;
1149 my $total = $wanted_memory + $wanted_swap;
1150 my $old_total = $old_memory + $old_swap;
1152 if ($total > $old_total) {
1153 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1154 "memory.memsw.limit_in_bytes",
1155 int($total*1024*1024));
1156 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1157 "memory.limit_in_bytes",
1158 int($wanted_memory*1024*1024));
1160 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1161 "memory.limit_in_bytes",
1162 int($wanted_memory*1024*1024));
1163 PVE
::LXC
::write_cgroup_value
("memory", $vmid,
1164 "memory.memsw.limit_in_bytes",
1165 int($total*1024*1024));
1167 $hotplug_memory_done = 1;
1170 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1171 # FIXME: $force deletion is not implemented for CTs
1172 foreach my $opt (sort keys %$pending_delete_hash) {
1173 next if $selection && !$selection->{$opt};
1175 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1177 } elsif ($opt =~ m/^unused(\d+)$/) {
1178 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1179 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1180 } elsif ($opt eq 'swap') {
1181 $hotplug_memory->(undef, 0);
1182 } elsif ($opt eq 'cpulimit') {
1183 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_period_us", -1);
1184 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1185 } elsif ($opt eq 'cpuunits') {
1186 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shared", $confdesc->{cpuunits
}->{default});
1187 } elsif ($opt =~ m/^net(\d)$/) {
1189 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1191 die "skip\n"; # skip non-hotpluggable opts
1195 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1197 delete $conf->{$opt};
1198 $class->remove_from_pending_delete($conf, $opt);
1202 foreach my $opt (sort keys %{$conf->{pending
}}) {
1203 next if $opt eq 'delete'; # just to be sure
1204 next if $selection && !$selection->{$opt};
1205 my $value = $conf->{pending
}->{$opt};
1207 if ($opt eq 'cpulimit') {
1208 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_period_us", 100000);
1209 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1210 } elsif ($opt eq 'cpuunits') {
1211 PVE
::LXC
::write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1212 } elsif ($opt =~ m/^net(\d+)$/) {
1214 my $net = $class->parse_lxc_network($value);
1215 $value = $class->print_lxc_network($net);
1216 PVE
::LXC
::update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1217 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1218 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1219 $hotplug_memory->($conf->{pending
}->{memory
}, $conf->{pending
}->{swap
});
1222 die "skip\n"; # skip non-hotpluggable
1226 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1228 $conf->{$opt} = $value;
1229 delete $conf->{pending
}->{$opt};
1233 $class->write_config($vmid, $conf);
1236 sub vmconfig_apply_pending
{
1237 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1239 my $add_apply_error = sub {
1240 my ($opt, $msg) = @_;
1241 my $err_msg = "unable to apply pending change $opt : $msg";
1242 $errors->{$opt} = $err_msg;
1246 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending
}->{delete});
1247 # FIXME: $force deletion is not implemented for CTs
1248 foreach my $opt (sort keys %$pending_delete_hash) {
1249 next if $selection && !$selection->{$opt};
1250 $class->cleanup_pending($conf);
1252 if ($opt =~ m/^mp(\d+)$/) {
1253 my $mp = $class->parse_ct_mountpoint($conf->{$opt});
1254 if ($mp->{type
} eq 'volume') {
1255 $class->add_unused_volume($conf, $mp->{volume
})
1256 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1258 } elsif ($opt =~ m/^unused(\d+)$/) {
1259 PVE
::LXC
::delete_mountpoint_volume
($storecfg, $vmid, $conf->{$opt})
1260 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1264 $add_apply_error->($opt, $err);
1266 delete $conf->{$opt};
1267 $class->remove_from_pending_delete($conf, $opt);
1271 foreach my $opt (sort keys %{$conf->{pending
}}) { # add/change
1272 next if $opt eq 'delete'; # just to be sure
1273 next if $selection && !$selection->{$opt};
1275 if ($opt =~ m/^mp(\d+)$/) {
1276 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1277 } elsif ($opt =~ m/^net(\d+)$/) {
1279 my $net = $class->parse_lxc_network($conf->{pending
}->{$opt});
1280 $conf->{pending
}->{$opt} = $class->print_lxc_network($net);
1284 $add_apply_error->($opt, $err);
1286 $class->cleanup_pending($conf);
1287 $conf->{$opt} = delete $conf->{pending
}->{$opt};
1291 $class->write_config($vmid, $conf);
1294 my $rescan_volume = sub {
1295 my ($storecfg, $mp) = @_;
1297 $mp->{size
} = PVE
::Storage
::volume_size_info
($storecfg, $mp->{volume
}, 5)
1298 if !defined($mp->{size
});
1300 warn "Could not rescan volume size - $@\n" if $@;
1303 sub apply_pending_mountpoint
{
1304 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1306 my $mp = $class->parse_ct_mountpoint($conf->{pending
}->{$opt});
1307 my $old = $conf->{$opt};
1308 if ($mp->{type
} eq 'volume') {
1309 if ($mp->{volume
} =~ $PVE::LXC
::NEW_DISK_RE
) {
1310 my $vollist = PVE
::LXC
::create_disks
(
1313 { $opt => $conf->{pending
}->{$opt} },
1318 $rescan_volume->($storecfg, $mp);
1319 $conf->{pending
}->{$opt} = $class->print_ct_mountpoint($mp);
1323 if (defined($old)) {
1324 my $mp = $class->parse_ct_mountpoint($old);
1325 if ($mp->{type
} eq 'volume') {
1326 $class->add_unused_volume($conf, $mp->{volume
})
1327 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1332 sub classify_mountpoint
{
1333 my ($class, $vol) = @_;
1334 if ($vol =~ m!^/!) {
1335 return 'device' if $vol =~ m!^/dev/!;
1341 my $__is_volume_in_use = sub {
1342 my ($class, $config, $volid) = @_;
1345 $class->foreach_mountpoint($config, sub {
1346 my ($ms, $mountpoint) = @_;
1348 $used = $mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid;
1354 sub is_volume_in_use_by_snapshots
{
1355 my ($class, $config, $volid) = @_;
1357 if (my $snapshots = $config->{snapshots
}) {
1358 foreach my $snap (keys %$snapshots) {
1359 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1366 sub is_volume_in_use
{
1367 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1368 return 1 if $__is_volume_in_use->($class, $config, $volid);
1369 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1370 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending
}, $volid);
1374 sub has_dev_console
{
1375 my ($class, $conf) = @_;
1377 return !(defined($conf->{console
}) && !$conf->{console
});
1381 my ($class, $conf, $keyname) = @_;
1383 if (my $lxcconf = $conf->{lxc
}) {
1384 foreach my $entry (@$lxcconf) {
1385 my ($key, undef) = @$entry;
1386 return 1 if $key eq $keyname;
1394 my ($class, $conf) = @_;
1396 return $conf->{tty
} // $confdesc->{tty
}->{default};
1400 my ($class, $conf) = @_;
1402 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1405 sub mountpoint_names
{
1406 my ($class, $reverse) = @_;
1408 my @names = ('rootfs');
1410 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1411 push @names, "mp$i";
1414 return $reverse ?
reverse @names : @names;
1417 sub foreach_mountpoint_full
{
1418 my ($class, $conf, $reverse, $func, @param) = @_;
1420 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1421 foreach my $key (@$mps) {
1422 my $value = $conf->{$key};
1423 my $mountpoint = $key eq 'rootfs' ?
$class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
1424 next if !defined($mountpoint);
1426 &$func($key, $mountpoint, @param);
1430 sub foreach_mountpoint
{
1431 my ($class, $conf, $func, @param) = @_;
1433 $class->foreach_mountpoint_full($conf, 0, $func, @param);
1436 sub foreach_mountpoint_reverse
{
1437 my ($class, $conf, $func, @param) = @_;
1439 $class->foreach_mountpoint_full($conf, 1, $func, @param);
1442 sub get_vm_volumes
{
1443 my ($class, $conf, $excludes) = @_;
1447 $class->foreach_mountpoint($conf, sub {
1448 my ($ms, $mountpoint) = @_;
1450 return if $excludes && $ms eq $excludes;
1452 my $volid = $mountpoint->{volume
};
1453 return if !$volid || $mountpoint->{type
} ne 'volume';
1455 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1458 push @$vollist, $volid;
1464 sub get_replicatable_volumes
{
1465 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1469 my $test_volid = sub {
1470 my ($volid, $mountpoint) = @_;
1474 my $mptype = $mountpoint->{type
};
1475 my $replicate = $mountpoint->{replicate
} // 1;
1477 if ($mptype ne 'volume') {
1478 # skip bindmounts if replicate = 0 even for cleanup,
1479 # since bind mounts could not have been replicated ever
1480 return if !$replicate;
1481 die "unable to replicate mountpoint type '$mptype'\n";
1484 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, $noerr);
1485 return if !$storeid;
1487 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1488 return if $scfg->{shared
};
1490 my ($path, $owner, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
1491 return if !$owner || ($owner != $vmid);
1493 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1495 return if !$cleanup && !$replicate;
1497 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'replicate', $volid)) {
1498 return if $cleanup || $noerr;
1499 die "missing replicate feature on volume '$volid'\n";
1502 $volhash->{$volid} = 1;
1505 $class->foreach_mountpoint($conf, sub {
1506 my ($ms, $mountpoint) = @_;
1507 $test_volid->($mountpoint->{volume
}, $mountpoint);
1510 foreach my $snapname (keys %{$conf->{snapshots
}}) {
1511 my $snap = $conf->{snapshots
}->{$snapname};
1512 $class->foreach_mountpoint($snap, sub {
1513 my ($ms, $mountpoint) = @_;
1514 $test_volid->($mountpoint->{volume
}, $mountpoint);
1518 # add 'unusedX' volumes to volhash
1519 foreach my $key (keys %$conf) {
1520 if ($key =~ m/^unused/) {
1521 $test_volid->($conf->{$key}, { type
=> 'volume', replicate
=> 1 });