]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Config.pm
network: Do not always reserve new IP in IPAM
[pve-container.git] / src / PVE / LXC / Config.pm
1 package PVE::LXC::Config;
2
3 use strict;
4 use warnings;
5
6 use Fcntl qw(O_RDONLY);
7
8 use PVE::AbstractConfig;
9 use PVE::Cluster qw(cfs_register_file);
10 use PVE::DataCenterConfig;
11 use PVE::GuestHelpers;
12 use PVE::INotify;
13 use PVE::JSONSchema qw(get_standard_option);
14 use PVE::Tools;
15
16 use PVE::LXC;
17
18 use base qw(PVE::AbstractConfig);
19
20 use constant {
21 FIFREEZE => 0xc0045877,
22 FITHAW => 0xc0045878,
23 };
24
25 my $have_sdn;
26 eval {
27 require PVE::Network::SDN::Vnets;
28 $have_sdn = 1;
29 };
30
31 my $nodename = PVE::INotify::nodename();
32 my $lock_handles = {};
33 my $lockdir = "/run/lock/lxc";
34 mkdir $lockdir;
35 mkdir "/etc/pve/nodes/$nodename/lxc";
36 my $MAX_MOUNT_POINTS = 256;
37 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
38
39 # BEGIN implemented abstract methods from PVE::AbstractConfig
40
41 sub guest_type {
42 return "CT";
43 }
44
45 sub __config_max_unused_disks {
46 my ($class) = @_;
47
48 return $MAX_UNUSED_DISKS;
49 }
50
51 sub config_file_lock {
52 my ($class, $vmid) = @_;
53
54 return "$lockdir/pve-config-${vmid}.lock";
55 }
56
57 sub cfs_config_path {
58 my ($class, $vmid, $node) = @_;
59
60 $node = $nodename if !$node;
61 return "nodes/$node/lxc/$vmid.conf";
62 }
63
64 sub mountpoint_backup_enabled {
65 my ($class, $mp_key, $mountpoint) = @_;
66
67 my $enabled;
68 my $reason;
69
70 if ($mp_key eq 'rootfs') {
71 $enabled = 1;
72 $reason = 'rootfs';
73 } elsif ($mountpoint->{type} ne 'volume') {
74 $enabled = 0;
75 $reason = 'not a volume';
76 } elsif ($mountpoint->{backup}) {
77 $enabled = 1;
78 $reason = 'enabled';
79 } else {
80 $enabled = 0;
81 $reason = 'disabled';
82 }
83 return wantarray ? ($enabled, $reason) : $enabled;
84 }
85
86 sub has_feature {
87 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
88 my $err;
89
90 my $opts;
91 if ($feature eq 'copy' || $feature eq 'clone') {
92 $opts = {'valid_target_formats' => ['raw', 'subvol']};
93 }
94
95 $class->foreach_volume($conf, sub {
96 my ($ms, $mountpoint) = @_;
97
98 return if $err; # skip further test
99 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
100
101 $err = 1 if !PVE::Storage::volume_has_feature(
102 $storecfg, $feature, $mountpoint->{volume}, $snapname, $running, $opts);
103 });
104
105 return $err ? 0 : 1;
106 }
107
108 sub __snapshot_save_vmstate {
109 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
110 die "implement me - snapshot_save_vmstate\n";
111 }
112
113 sub __snapshot_activate_storages {
114 my ($class, $conf, $include_vmstate) = @_;
115
116 my $storecfg = PVE::Storage::config();
117 my $opts = $include_vmstate ? { 'extra_keys' => ['vmstate'] } : {};
118 my $storage_hash = {};
119
120 $class->foreach_volume_full($conf, $opts, sub {
121 my ($vs, $mountpoint) = @_;
122
123 return if $mountpoint->{type} ne 'volume';
124
125 my ($storeid) = PVE::Storage::parse_volume_id($mountpoint->{volume});
126 $storage_hash->{$storeid} = 1;
127 });
128
129 PVE::Storage::activate_storage_list($storecfg, [ sort keys $storage_hash->%* ]);
130 }
131
132 sub __snapshot_check_running {
133 my ($class, $vmid) = @_;
134 return PVE::LXC::check_running($vmid);
135 }
136
137 sub __snapshot_check_freeze_needed {
138 my ($class, $vmid, $config, $save_vmstate) = @_;
139
140 my $ret = $class->__snapshot_check_running($vmid);
141 return ($ret, $ret);
142 }
143
144 # implements similar functionality to fsfreeze(8)
145 sub fsfreeze_mountpoint {
146 my ($path, $thaw) = @_;
147
148 my $op = $thaw ? 'thaw' : 'freeze';
149 my $ioctl = $thaw ? FITHAW : FIFREEZE;
150
151 sysopen my $fd, $path, O_RDONLY or die "failed to open $path: $!\n";
152 my $ioctl_err;
153 if (!ioctl($fd, $ioctl, 0)) {
154 $ioctl_err = "$!";
155 }
156 close($fd);
157 die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
158 }
159
160 sub __snapshot_freeze {
161 my ($class, $vmid, $unfreeze) = @_;
162
163 my $conf = $class->load_config($vmid);
164 my $storagecfg = PVE::Storage::config();
165
166 my $freeze_mps = [];
167 $class->foreach_volume($conf, sub {
168 my ($ms, $mountpoint) = @_;
169
170 return if $mountpoint->{type} ne 'volume';
171
172 if (PVE::Storage::volume_snapshot_needs_fsfreeze($storagecfg, $mountpoint->{volume})) {
173 push @$freeze_mps, $mountpoint->{mp};
174 }
175 });
176
177 my $freeze_mountpoints = sub {
178 my ($thaw) = @_;
179
180 return if scalar(@$freeze_mps) == 0;
181
182 my $pid = PVE::LXC::find_lxc_pid($vmid);
183
184 for my $mp (@$freeze_mps) {
185 eval{ fsfreeze_mountpoint("/proc/${pid}/root/${mp}", $thaw); };
186 warn $@ if $@;
187 }
188 };
189
190 if ($unfreeze) {
191 eval { PVE::LXC::thaw($vmid); };
192 warn $@ if $@;
193 $freeze_mountpoints->(1);
194 } else {
195 PVE::LXC::freeze($vmid);
196 PVE::LXC::sync_container_namespace($vmid);
197 $freeze_mountpoints->(0);
198 }
199 }
200
201 sub __snapshot_create_vol_snapshot {
202 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
203
204 my $storecfg = PVE::Storage::config();
205
206 return if $snapname eq 'vzdump' &&
207 !$class->mountpoint_backup_enabled($ms, $mountpoint);
208
209 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
210 }
211
212 sub __snapshot_delete_remove_drive {
213 my ($class, $snap, $remove_drive) = @_;
214
215 if ($remove_drive eq 'vmstate') {
216 die "implement me - saving vmstate\n";
217 } else {
218 my $value = $snap->{$remove_drive};
219 my $mountpoint = $class->parse_volume($remove_drive, $value, 1);
220 delete $snap->{$remove_drive};
221
222 $class->add_unused_volume($snap, $mountpoint->{volume})
223 if $mountpoint && ($mountpoint->{type} eq 'volume');
224 }
225 }
226
227 sub __snapshot_delete_vmstate_file {
228 my ($class, $snap, $force) = @_;
229
230 die "implement me - saving vmstate\n";
231 }
232
233 sub __snapshot_delete_vol_snapshot {
234 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
235
236 return if $snapname eq 'vzdump' &&
237 !$class->mountpoint_backup_enabled($ms, $mountpoint);
238
239 my $storecfg = PVE::Storage::config();
240 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
241 push @$unused, $mountpoint->{volume};
242 }
243
244 sub __snapshot_rollback_vol_possible {
245 my ($class, $mountpoint, $snapname, $blockers) = @_;
246
247 my $storecfg = PVE::Storage::config();
248 PVE::Storage::volume_rollback_is_possible(
249 $storecfg,
250 $mountpoint->{volume},
251 $snapname,
252 $blockers,
253 );
254 }
255
256 sub __snapshot_rollback_vol_rollback {
257 my ($class, $mountpoint, $snapname) = @_;
258
259 my $storecfg = PVE::Storage::config();
260 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
261 }
262
263 sub __snapshot_rollback_vm_stop {
264 my ($class, $vmid) = @_;
265
266 PVE::LXC::vm_stop($vmid, 1)
267 if $class->__snapshot_check_running($vmid);
268 }
269
270 sub __snapshot_rollback_vm_start {
271 my ($class, $vmid, $vmstate, $data);
272
273 die "implement me - save vmstate\n";
274 }
275
276 sub __snapshot_rollback_get_unused {
277 my ($class, $conf, $snap) = @_;
278
279 my $unused = [];
280
281 $class->foreach_volume($conf, sub {
282 my ($vs, $volume) = @_;
283
284 return if $volume->{type} ne 'volume';
285
286 my $found = 0;
287 my $volid = $volume->{volume};
288
289 $class->foreach_volume($snap, sub {
290 my ($ms, $mountpoint) = @_;
291
292 return if $found;
293 return if ($mountpoint->{type} ne 'volume');
294
295 $found = 1
296 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
297 });
298
299 push @$unused, $volid if !$found;
300 });
301
302 return $unused;
303 }
304
305 # END implemented abstract methods from PVE::AbstractConfig
306
307 # BEGIN JSON config code
308
309 cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
310
311
312 my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
313
314 sub is_valid_mount_option {
315 my ($option) = @_;
316 return $option =~ $valid_mount_option_re;
317 }
318
319 my $rootfs_desc = {
320 volume => {
321 type => 'string',
322 default_key => 1,
323 format => 'pve-lxc-mp-string',
324 format_description => 'volume',
325 description => 'Volume, device or directory to mount into the container.',
326 },
327 size => {
328 type => 'string',
329 format => 'disk-size',
330 format_description => 'DiskSize',
331 description => 'Volume size (read only value).',
332 optional => 1,
333 },
334 acl => {
335 type => 'boolean',
336 description => 'Explicitly enable or disable ACL support.',
337 optional => 1,
338 },
339 mountoptions => {
340 optional => 1,
341 type => 'string',
342 description => 'Extra mount options for rootfs/mps.',
343 format_description => 'opt[;opt...]',
344 pattern => qr/$valid_mount_option_re(;$valid_mount_option_re)*/,
345 },
346 ro => {
347 type => 'boolean',
348 description => 'Read-only mount point',
349 optional => 1,
350 },
351 quota => {
352 type => 'boolean',
353 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
354 optional => 1,
355 },
356 replicate => {
357 type => 'boolean',
358 description => 'Will include this volume to a storage replica job.',
359 optional => 1,
360 default => 1,
361 },
362 shared => {
363 type => 'boolean',
364 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
365 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!",
366 optional => 1,
367 default => 0,
368 },
369 };
370
371 PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
372 type => 'string', format => $rootfs_desc,
373 description => "Use volume as container root.",
374 optional => 1,
375 });
376
377 # IP address with optional interface suffix for link local ipv6 addresses
378 PVE::JSONSchema::register_format('lxc-ip-with-ll-iface', \&verify_ip_with_ll_iface);
379 sub verify_ip_with_ll_iface {
380 my ($addr, $noerr) = @_;
381
382 if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
383 if (PVE::JSONSchema::pve_verify_ip($addr, 1)
384 && PVE::JSONSchema::pve_verify_iface($iface, 1))
385 {
386 return $addr;
387 }
388 }
389
390 return PVE::JSONSchema::pve_verify_ip($addr, $noerr);
391 }
392
393
394 my $features_desc = {
395 mount => {
396 optional => 1,
397 type => 'string',
398 description => "Allow mounting file systems of specific types."
399 ." This should be a list of file system types as used with the mount command."
400 ." Note that this can have negative effects on the container's security."
401 ." With access to a loop device, mounting a file can circumvent the mknod"
402 ." permission of the devices cgroup, mounting an NFS file system can"
403 ." block the host's I/O completely and prevent it from rebooting, etc.",
404 format_description => 'fstype;fstype;...',
405 pattern => qr/[a-zA-Z0-9_; ]+/,
406 },
407 nesting => {
408 optional => 1,
409 type => 'boolean',
410 default => 0,
411 description => "Allow nesting."
412 ." Best used with unprivileged containers with additional id mapping."
413 ." Note that this will expose procfs and sysfs contents of the host"
414 ." to the guest.",
415 },
416 keyctl => {
417 optional => 1,
418 type => 'boolean',
419 default => 0,
420 description => "For unprivileged containers only: Allow the use of the keyctl() system call."
421 ." This is required to use docker inside a container."
422 ." By default unprivileged containers will see this system call as non-existent."
423 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
424 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
425 ." Essentially, you can choose between running systemd-networkd or docker.",
426 },
427 fuse => {
428 optional => 1,
429 type => 'boolean',
430 default => 0,
431 description => "Allow using 'fuse' file systems in a container."
432 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
433 },
434 mknod => {
435 optional => 1,
436 type => 'boolean',
437 default => 0,
438 description => "Allow unprivileged containers to use mknod() to add certain device nodes."
439 ." This requires a kernel with seccomp trap to user space support (5.3 or newer)."
440 ." This is experimental.",
441 },
442 force_rw_sys => {
443 optional => 1,
444 type => 'boolean',
445 default => 0,
446 description => "Mount /sys in unprivileged containers as `rw` instead of `mixed`."
447 ." This can break networking under newer (>= v245) systemd-network use."
448 },
449 };
450
451 my $confdesc = {
452 lock => {
453 optional => 1,
454 type => 'string',
455 description => "Lock/unlock the container.",
456 enum => [qw(backup create destroyed disk fstrim migrate mounted rollback snapshot snapshot-delete)],
457 },
458 onboot => {
459 optional => 1,
460 type => 'boolean',
461 description => "Specifies whether a container will be started during system bootup.",
462 default => 0,
463 },
464 startup => get_standard_option('pve-startup-order'),
465 template => {
466 optional => 1,
467 type => 'boolean',
468 description => "Enable/disable Template.",
469 default => 0,
470 },
471 arch => {
472 optional => 1,
473 type => 'string',
474 enum => ['amd64', 'i386', 'arm64', 'armhf', 'riscv32', 'riscv64'],
475 description => "OS architecture type.",
476 default => 'amd64',
477 },
478 ostype => {
479 optional => 1,
480 type => 'string',
481 enum => [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
482 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.",
483 },
484 console => {
485 optional => 1,
486 type => 'boolean',
487 description => "Attach a console device (/dev/console) to the container.",
488 default => 1,
489 },
490 tty => {
491 optional => 1,
492 type => 'integer',
493 description => "Specify the number of tty available to the container",
494 minimum => 0,
495 maximum => 6,
496 default => 2,
497 },
498 cores => {
499 optional => 1,
500 type => 'integer',
501 description => "The number of cores assigned to the container. A container can use all available cores by default.",
502 minimum => 1,
503 maximum => 8192,
504 },
505 cpulimit => {
506 optional => 1,
507 type => 'number',
508 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.",
509 minimum => 0,
510 maximum => 8192,
511 default => 0,
512 },
513 cpuunits => {
514 optional => 1,
515 type => 'integer',
516 description => "CPU weight for a container, will be clamped to [1, 10000] in cgroup v2.",
517 verbose_description => "CPU weight for a container. Argument is used in the kernel fair "
518 ."scheduler. The larger the number is, the more CPU time this container gets. Number "
519 ."is relative to the weights of all the other running guests.",
520 minimum => 0,
521 maximum => 500000,
522 default => 'cgroup v1: 1024, cgroup v2: 100',
523 },
524 memory => {
525 optional => 1,
526 type => 'integer',
527 description => "Amount of RAM for the container in MB.",
528 minimum => 16,
529 default => 512,
530 },
531 swap => {
532 optional => 1,
533 type => 'integer',
534 description => "Amount of SWAP for the container in MB.",
535 minimum => 0,
536 default => 512,
537 },
538 hostname => {
539 optional => 1,
540 description => "Set a host name for the container.",
541 type => 'string', format => 'dns-name',
542 maxLength => 255,
543 },
544 description => {
545 optional => 1,
546 type => 'string',
547 description => "Description for the Container. Shown in the web-interface CT's summary."
548 ." This is saved as comment inside the configuration file.",
549 maxLength => 1024 * 8,
550 },
551 searchdomain => {
552 optional => 1,
553 type => 'string', format => 'dns-name-list',
554 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
555 },
556 nameserver => {
557 optional => 1,
558 type => 'string', format => 'lxc-ip-with-ll-iface-list',
559 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.",
560 },
561 timezone => {
562 optional => 1,
563 type => 'string', format => 'pve-ct-timezone',
564 description => "Time zone to use in the container. If option isn't set, then nothing will be done. Can be set to 'host' to match the host time zone, or an arbitrary time zone option from /usr/share/zoneinfo/zone.tab",
565 },
566 rootfs => get_standard_option('pve-ct-rootfs'),
567 parent => {
568 optional => 1,
569 type => 'string', format => 'pve-configid',
570 maxLength => 40,
571 description => "Parent snapshot name. This is used internally, and should not be modified.",
572 },
573 snaptime => {
574 optional => 1,
575 description => "Timestamp for snapshots.",
576 type => 'integer',
577 minimum => 0,
578 },
579 cmode => {
580 optional => 1,
581 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).",
582 type => 'string',
583 enum => ['shell', 'console', 'tty'],
584 default => 'tty',
585 },
586 protection => {
587 optional => 1,
588 type => 'boolean',
589 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
590 default => 0,
591 },
592 unprivileged => {
593 optional => 1,
594 type => 'boolean',
595 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
596 default => 0,
597 },
598 features => {
599 optional => 1,
600 type => 'string',
601 format => $features_desc,
602 description => "Allow containers access to advanced features.",
603 },
604 hookscript => {
605 optional => 1,
606 type => 'string',
607 format => 'pve-volume-id',
608 description => 'Script that will be exectued during various steps in the containers lifetime.',
609 },
610 tags => {
611 type => 'string', format => 'pve-tag-list',
612 description => 'Tags of the Container. This is only meta information.',
613 optional => 1,
614 },
615 debug => {
616 optional => 1,
617 type => 'boolean',
618 description => "Try to be more verbose. For now this only enables debug log-level on start.",
619 default => 0,
620 },
621 };
622
623 my $valid_lxc_conf_keys = {
624 'lxc.apparmor.profile' => 1,
625 'lxc.apparmor.allow_incomplete' => 1,
626 'lxc.apparmor.allow_nesting' => 1,
627 'lxc.apparmor.raw' => 1,
628 'lxc.selinux.context' => 1,
629 'lxc.include' => 1,
630 'lxc.arch' => 1,
631 'lxc.uts.name' => 1,
632 'lxc.signal.halt' => 1,
633 'lxc.signal.reboot' => 1,
634 'lxc.signal.stop' => 1,
635 'lxc.init.cmd' => 1,
636 'lxc.pty.max' => 1,
637 'lxc.console.logfile' => 1,
638 'lxc.console.path' => 1,
639 'lxc.tty.max' => 1,
640 'lxc.devtty.dir' => 1,
641 'lxc.hook.autodev' => 1,
642 'lxc.autodev' => 1,
643 'lxc.kmsg' => 1,
644 'lxc.mount.fstab' => 1,
645 'lxc.mount.entry' => 1,
646 'lxc.mount.auto' => 1,
647 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
648 'lxc.rootfs.mount' => 1,
649 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
650 ', please use mount point options in the "rootfs" key',
651 # lxc.cgroup.*
652 # lxc.prlimit.*
653 # lxc.net.*
654 'lxc.cap.drop' => 1,
655 'lxc.cap.keep' => 1,
656 'lxc.seccomp.profile' => 1,
657 'lxc.seccomp.notify.proxy' => 1,
658 'lxc.seccomp.notify.cookie' => 1,
659 'lxc.idmap' => 1,
660 'lxc.hook.pre-start' => 1,
661 'lxc.hook.pre-mount' => 1,
662 'lxc.hook.mount' => 1,
663 'lxc.hook.start' => 1,
664 'lxc.hook.stop' => 1,
665 'lxc.hook.post-stop' => 1,
666 'lxc.hook.clone' => 1,
667 'lxc.hook.destroy' => 1,
668 'lxc.hook.version' => 1,
669 'lxc.log.level' => 1,
670 'lxc.log.file' => 1,
671 'lxc.start.auto' => 1,
672 'lxc.start.delay' => 1,
673 'lxc.start.order' => 1,
674 'lxc.group' => 1,
675 'lxc.environment' => 1,
676
677 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
678 'lxc.sysctl.fs.mqueue' => 1,
679 'lxc.sysctl.kernel.msgmax' => 1,
680 'lxc.sysctl.kernel.msgmnb' => 1,
681 'lxc.sysctl.kernel.msgmni' => 1,
682 'lxc.sysctl.kernel.sem' => 1,
683 'lxc.sysctl.kernel.shmall' => 1,
684 'lxc.sysctl.kernel.shmmax' => 1,
685 'lxc.sysctl.kernel.shmmni' => 1,
686 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
687 };
688
689 my $deprecated_lxc_conf_keys = {
690 # Deprecated (removed with lxc 3.0):
691 'lxc.aa_profile' => 'lxc.apparmor.profile',
692 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
693 'lxc.console' => 'lxc.console.path',
694 'lxc.devttydir' => 'lxc.tty.dir',
695 'lxc.haltsignal' => 'lxc.signal.halt',
696 'lxc.rebootsignal' => 'lxc.signal.reboot',
697 'lxc.stopsignal' => 'lxc.signal.stop',
698 'lxc.id_map' => 'lxc.idmap',
699 'lxc.init_cmd' => 'lxc.init.cmd',
700 'lxc.loglevel' => 'lxc.log.level',
701 'lxc.logfile' => 'lxc.log.file',
702 'lxc.mount' => 'lxc.mount.fstab',
703 'lxc.network.type' => 'lxc.net.INDEX.type',
704 'lxc.network.flags' => 'lxc.net.INDEX.flags',
705 'lxc.network.link' => 'lxc.net.INDEX.link',
706 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
707 'lxc.network.name' => 'lxc.net.INDEX.name',
708 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
709 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
710 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
711 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
712 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
713 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
714 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
715 'lxc.pts' => 'lxc.pty.max',
716 'lxc.se_context' => 'lxc.selinux.context',
717 'lxc.seccomp' => 'lxc.seccomp.profile',
718 'lxc.tty' => 'lxc.tty.max',
719 'lxc.utsname' => 'lxc.uts.name',
720 };
721
722 sub is_valid_lxc_conf_key {
723 my ($vmid, $key) = @_;
724 if ($key =~ /^lxc\.limit\./) {
725 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
726 return 1;
727 }
728 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
729 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
730 return 1;
731 }
732 my $validity = $valid_lxc_conf_keys->{$key};
733 return $validity if defined($validity);
734 return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
735 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
736 || $key =~ /^lxc\.net\./; # allow custom network definitions
737 return 0;
738 }
739
740 our $netconf_desc = {
741 type => {
742 type => 'string',
743 optional => 1,
744 description => "Network interface type.",
745 enum => [qw(veth)],
746 },
747 name => {
748 type => 'string',
749 format_description => 'string',
750 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
751 pattern => '[-_.\w\d]+',
752 },
753 bridge => {
754 type => 'string',
755 format_description => 'bridge',
756 description => 'Bridge to attach the network device to.',
757 pattern => '[-_.\w\d]+',
758 optional => 1,
759 },
760 hwaddr => get_standard_option('mac-addr', {
761 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)',
762 }),
763 mtu => {
764 type => 'integer',
765 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
766 minimum => 64, # minimum ethernet frame is 64 bytes
767 maximum => 65535,
768 optional => 1,
769 },
770 ip => {
771 type => 'string',
772 format => 'pve-ipv4-config',
773 format_description => '(IPv4/CIDR|dhcp|manual)',
774 description => 'IPv4 address in CIDR format.',
775 optional => 1,
776 },
777 gw => {
778 type => 'string',
779 format => 'ipv4',
780 format_description => 'GatewayIPv4',
781 description => 'Default gateway for IPv4 traffic.',
782 optional => 1,
783 },
784 ip6 => {
785 type => 'string',
786 format => 'pve-ipv6-config',
787 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
788 description => 'IPv6 address in CIDR format.',
789 optional => 1,
790 },
791 gw6 => {
792 type => 'string',
793 format => 'ipv6',
794 format_description => 'GatewayIPv6',
795 description => 'Default gateway for IPv6 traffic.',
796 optional => 1,
797 },
798 firewall => {
799 type => 'boolean',
800 description => "Controls whether this interface's firewall rules should be used.",
801 optional => 1,
802 },
803 tag => {
804 type => 'integer',
805 minimum => 1,
806 maximum => 4094,
807 description => "VLAN tag for this interface.",
808 optional => 1,
809 },
810 trunks => {
811 type => 'string',
812 pattern => qr/\d+(?:;\d+)*/,
813 format_description => 'vlanid[;vlanid...]',
814 description => "VLAN ids to pass through the interface",
815 optional => 1,
816 },
817 rate => {
818 type => 'number',
819 format_description => 'mbps',
820 description => "Apply rate limiting to the interface",
821 optional => 1,
822 },
823 # TODO: Rename this option and the qemu-server one to `link-down` for PVE 8.0
824 link_down => {
825 type => 'boolean',
826 description => 'Whether this interface should be disconnected (like pulling the plug).',
827 optional => 1,
828 },
829 };
830 PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
831
832 my $MAX_LXC_NETWORKS = 32;
833 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
834 $confdesc->{"net$i"} = {
835 optional => 1,
836 type => 'string', format => $netconf_desc,
837 description => "Specifies network interfaces for the container.",
838 };
839 }
840
841 PVE::JSONSchema::register_format('pve-ct-timezone', \&verify_ct_timezone);
842 sub verify_ct_timezone {
843 my ($timezone, $noerr) = @_;
844
845 return if $timezone eq 'host'; # using host settings
846
847 PVE::JSONSchema::pve_verify_timezone($timezone);
848 }
849
850 PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
851 sub verify_lxc_mp_string {
852 my ($mp, $noerr) = @_;
853
854 # do not allow:
855 # /./ or /../
856 # /. or /.. at the end
857 # ../ at the beginning
858
859 if($mp =~ m@/\.\.?/@ ||
860 $mp =~ m@/\.\.?$@ ||
861 $mp =~ m@^\.\./@) {
862 return undef if $noerr;
863 die "$mp contains illegal character sequences\n";
864 }
865 return $mp;
866 }
867
868 my $mp_desc = {
869 %$rootfs_desc,
870 backup => {
871 type => 'boolean',
872 description => 'Whether to include the mount point in backups.',
873 verbose_description => 'Whether to include the mount point in backups '.
874 '(only used for volume mount points).',
875 optional => 1,
876 },
877 mp => {
878 type => 'string',
879 format => 'pve-lxc-mp-string',
880 format_description => 'Path',
881 description => 'Path to the mount point as seen from inside the container '.
882 '(must not contain symlinks).',
883 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
884 "NOTE: Must not contain any symlinks for security reasons."
885 },
886 };
887 PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
888
889 my $unused_desc = {
890 volume => {
891 type => 'string',
892 default_key => 1,
893 format => 'pve-volume-id',
894 format_description => 'volume',
895 description => 'The volume that is not used currently.',
896 }
897 };
898
899 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
900 $confdesc->{"mp$i"} = {
901 optional => 1,
902 type => 'string', format => $mp_desc,
903 description => "Use volume as container mount point. Use the special " .
904 "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
905 optional => 1,
906 };
907 }
908
909 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
910 $confdesc->{"unused$i"} = {
911 optional => 1,
912 type => 'string', format => $unused_desc,
913 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
914 }
915 }
916
917 sub parse_pct_config {
918 my ($filename, $raw, $strict) = @_;
919
920 return undef if !defined($raw);
921
922 my $res = {
923 digest => Digest::SHA::sha1_hex($raw),
924 snapshots => {},
925 pending => {},
926 };
927
928 my $handle_error = sub {
929 my ($msg) = @_;
930
931 if ($strict) {
932 die $msg;
933 } else {
934 warn $msg;
935 }
936 };
937
938 $filename =~ m|/lxc/(\d+).conf$|
939 || die "got strange filename '$filename'";
940
941 my $vmid = $1;
942
943 my $conf = $res;
944 my $descr = '';
945 my $section = '';
946
947 my @lines = split(/\n/, $raw);
948 foreach my $line (@lines) {
949 next if $line =~ m/^\s*$/;
950
951 if ($line =~ m/^\[pve:pending\]\s*$/i) {
952 $section = 'pending';
953 $conf->{description} = $descr if $descr;
954 $descr = '';
955 $conf = $res->{$section} = {};
956 next;
957 } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
958 $section = $1;
959 $conf->{description} = $descr if $descr;
960 $descr = '';
961 $conf = $res->{snapshots}->{$section} = {};
962 next;
963 }
964
965 if ($line =~ m/^\#(.*)$/) {
966 $descr .= PVE::Tools::decode_text($1) . "\n";
967 next;
968 }
969
970 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
971 my $key = $1;
972 my $value = $3;
973 my $validity = is_valid_lxc_conf_key($vmid, $key);
974 if ($validity eq 1) {
975 push @{$conf->{lxc}}, [$key, $value];
976 } elsif (my $errmsg = $validity) {
977 $handle_error->("vm $vmid - $key: $errmsg\n");
978 } else {
979 $handle_error->("vm $vmid - unable to parse config: $line\n");
980 }
981 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
982 $descr .= PVE::Tools::decode_text($2);
983 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
984 $conf->{snapstate} = $1;
985 } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) {
986 my $value = $1;
987 if ($section eq 'pending') {
988 $conf->{delete} = $value;
989 } else {
990 $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
991 }
992 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
993 my $key = $1;
994 my $value = $2;
995 eval { $value = PVE::LXC::Config->check_type($key, $value); };
996 $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
997 $conf->{$key} = $value;
998 } else {
999 $handle_error->("vm $vmid - unable to parse config: $line\n");
1000 }
1001 }
1002
1003 $conf->{description} = $descr if $descr;
1004
1005 delete $res->{snapstate}; # just to be sure
1006
1007 return $res;
1008 }
1009
1010 sub write_pct_config {
1011 my ($filename, $conf) = @_;
1012
1013 delete $conf->{snapstate}; # just to be sure
1014
1015 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
1016 my $used_volids = {};
1017 foreach my $vid (@$volidlist) {
1018 $used_volids->{$vid} = 1;
1019 }
1020
1021 # remove 'unusedX' settings if the volume is still used
1022 foreach my $key (keys %$conf) {
1023 my $value = $conf->{$key};
1024 if ($key =~ m/^unused/ && $used_volids->{$value}) {
1025 delete $conf->{$key};
1026 }
1027 }
1028
1029 my $generate_raw_config = sub {
1030 my ($conf) = @_;
1031
1032 my $raw = '';
1033
1034 # add description as comment to top of file
1035 my $descr = $conf->{description} || '';
1036 foreach my $cl (split(/\n/, $descr)) {
1037 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
1038 }
1039
1040 foreach my $key (sort keys %$conf) {
1041 next if $key eq 'digest' || $key eq 'description' ||
1042 $key eq 'pending' || $key eq 'snapshots' ||
1043 $key eq 'snapname' || $key eq 'lxc';
1044 my $value = $conf->{$key};
1045 die "detected invalid newline inside property '$key'\n"
1046 if $value =~ m/\n/;
1047 $raw .= "$key: $value\n";
1048 }
1049
1050 if (my $lxcconf = $conf->{lxc}) {
1051 foreach my $entry (@$lxcconf) {
1052 my ($k, $v) = @$entry;
1053 $raw .= "$k: $v\n";
1054 }
1055 }
1056
1057 return $raw;
1058 };
1059
1060 my $raw = &$generate_raw_config($conf);
1061
1062 if (scalar(keys %{$conf->{pending}})){
1063 $raw .= "\n[pve:pending]\n";
1064 $raw .= &$generate_raw_config($conf->{pending});
1065 }
1066
1067 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
1068 $raw .= "\n[$snapname]\n";
1069 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
1070 }
1071
1072 return $raw;
1073 }
1074
1075 sub update_pct_config {
1076 my ($class, $vmid, $conf, $running, $param, $delete, $revert) = @_;
1077
1078 my $storage_cfg = PVE::Storage::config();
1079
1080 foreach my $opt (@$revert) {
1081 delete $conf->{pending}->{$opt};
1082 $class->remove_from_pending_delete($conf, $opt); # also remove from deletion queue
1083 }
1084
1085 # write updates to pending section
1086 my $modified = {}; # record modified options
1087
1088 foreach my $opt (@$delete) {
1089 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
1090 warn "cannot delete '$opt' - not set in current configuration!\n";
1091 next;
1092 }
1093 $modified->{$opt} = 1;
1094 if ($opt eq 'memory' || $opt eq 'rootfs' || $opt eq 'ostype') {
1095 die "unable to delete required option '$opt'\n";
1096 } elsif ($opt =~ m/^unused(\d+)$/) {
1097 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1098 } elsif ($opt =~ m/^mp(\d+)$/) {
1099 $class->check_protection($conf, "can't remove CT $vmid drive '$opt'");
1100 } elsif ($opt eq 'unprivileged') {
1101 die "unable to delete read-only option: '$opt'\n";
1102 }
1103 $class->add_to_pending_delete($conf, $opt);
1104 }
1105
1106 my $check_content_type = sub {
1107 my ($mp) = @_;
1108 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
1109 my $storage_config = PVE::Storage::storage_config($storage_cfg, $sid);
1110 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
1111 if !$storage_config->{content}->{rootdir};
1112 };
1113
1114 foreach my $opt (sort keys %$param) { # add/change
1115 $modified->{$opt} = 1;
1116 my $value = $param->{$opt};
1117 if ($opt =~ m/^mp(\d+)$/ || $opt eq 'rootfs') {
1118 $class->check_protection($conf, "can't update CT $vmid drive '$opt'");
1119 my $mp = $class->parse_volume($opt, $value);
1120 $check_content_type->($mp) if ($mp->{type} eq 'volume');
1121 } elsif ($opt eq 'hookscript') {
1122 PVE::GuestHelpers::check_hookscript($value);
1123 } elsif ($opt eq 'nameserver') {
1124 $value = PVE::LXC::verify_nameserver_list($value);
1125 } elsif ($opt eq 'searchdomain') {
1126 $value = PVE::LXC::verify_searchdomain_list($value);
1127 } elsif ($opt eq 'unprivileged') {
1128 die "unable to modify read-only option: '$opt'\n";
1129 } elsif ($opt eq 'tags') {
1130 $value = PVE::GuestHelpers::get_unique_tags($value);
1131 } elsif ($opt =~ m/^net(\d+)$/) {
1132 my $res = PVE::JSONSchema::parse_property_string($netconf_desc, $value);
1133
1134 if (my $mtu = $res->{mtu}) {
1135 my $bridge_mtu = PVE::Network::read_bridge_mtu($res->{bridge});
1136 die "$opt: MTU size '$mtu' is bigger than bridge MTU '$bridge_mtu'\n"
1137 if ($mtu > $bridge_mtu);
1138 }
1139 }
1140 $conf->{pending}->{$opt} = $value;
1141 $class->remove_from_pending_delete($conf, $opt);
1142 }
1143
1144 my $changes = $class->cleanup_pending($conf);
1145
1146 my $errors = {};
1147 if ($running) {
1148 $class->vmconfig_hotplug_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1149 } else {
1150 $class->vmconfig_apply_pending($vmid, $conf, $storage_cfg, $modified, $errors);
1151 }
1152
1153 return $errors;
1154 }
1155
1156 sub check_type {
1157 my ($class, $key, $value) = @_;
1158
1159 die "unknown setting '$key'\n" if !$confdesc->{$key};
1160
1161 my $type = $confdesc->{$key}->{type};
1162
1163 if (!defined($value)) {
1164 die "got undefined value\n";
1165 }
1166
1167 if ($value =~ m/[\n\r]/) {
1168 die "property contains a line feed\n";
1169 }
1170
1171 if ($type eq 'boolean') {
1172 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1173 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1174 die "type check ('boolean') failed - got '$value'\n";
1175 } elsif ($type eq 'integer') {
1176 return int($1) if $value =~ m/^(\d+)$/;
1177 die "type check ('integer') failed - got '$value'\n";
1178 } elsif ($type eq 'number') {
1179 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1180 die "type check ('number') failed - got '$value'\n";
1181 } elsif ($type eq 'string') {
1182 if (my $fmt = $confdesc->{$key}->{format}) {
1183 PVE::JSONSchema::check_format($fmt, $value);
1184 return $value;
1185 }
1186 return $value;
1187 } else {
1188 die "internal error"
1189 }
1190 }
1191
1192
1193 # add JSON properties for create and set function
1194 sub json_config_properties {
1195 my ($class, $prop) = @_;
1196
1197 foreach my $opt (keys %$confdesc) {
1198 next if $opt eq 'parent' || $opt eq 'snaptime';
1199 next if $prop->{$opt};
1200 $prop->{$opt} = $confdesc->{$opt};
1201 }
1202
1203 return $prop;
1204 }
1205
1206 my $parse_ct_mountpoint_full = sub {
1207 my ($class, $desc, $data, $noerr) = @_;
1208
1209 $data //= '';
1210
1211 my $res;
1212 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1213 if ($@) {
1214 return undef if $noerr;
1215 die $@;
1216 }
1217
1218 if (defined(my $size = $res->{size})) {
1219 $size = PVE::JSONSchema::parse_size($size);
1220 if (!defined($size)) {
1221 return undef if $noerr;
1222 die "invalid size: $size\n";
1223 }
1224 $res->{size} = $size;
1225 }
1226
1227 $res->{type} = $class->classify_mountpoint($res->{volume});
1228
1229 return $res;
1230 };
1231
1232 sub print_ct_mountpoint {
1233 my ($class, $info, $nomp) = @_;
1234 my $skip = [ 'type' ];
1235 push @$skip, 'mp' if $nomp;
1236 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1237 }
1238
1239 sub print_ct_unused {
1240 my ($class, $info) = @_;
1241
1242 my $skip = [ 'type' ];
1243 return PVE::JSONSchema::print_property_string($info, $unused_desc, $skip);
1244 }
1245
1246 sub parse_volume {
1247 my ($class, $key, $volume_string, $noerr) = @_;
1248
1249 if ($key eq 'rootfs') {
1250 my $res = $parse_ct_mountpoint_full->($class, $rootfs_desc, $volume_string, $noerr);
1251 $res->{mp} = '/' if defined($res);
1252 return $res;
1253 } elsif ($key =~ m/^mp\d+$/) {
1254 return $parse_ct_mountpoint_full->($class, $mp_desc, $volume_string, $noerr);
1255 } elsif ($key =~ m/^unused\d+$/) {
1256 return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
1257 }
1258
1259 die "parse_volume - unknown type: $key\n" if !$noerr;
1260
1261 return;
1262 }
1263
1264 sub print_volume {
1265 my ($class, $key, $volume) = @_;
1266
1267 return $class->print_ct_unused($volume) if $key =~ m/^unused(\d+)$/;
1268
1269 return $class->print_ct_mountpoint($volume, $key eq 'rootfs');
1270 }
1271
1272 sub volid_key {
1273 my ($class) = @_;
1274
1275 return 'volume';
1276 }
1277
1278 sub print_lxc_network {
1279 my ($class, $net) = @_;
1280 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1281 }
1282
1283 sub parse_lxc_network {
1284 my ($class, $data) = @_;
1285
1286 return {} if !$data;
1287
1288 my $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1289
1290 $res->{type} = 'veth';
1291 if (!$res->{hwaddr}) {
1292 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1293 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1294 }
1295
1296 return $res;
1297 }
1298
1299 sub parse_features {
1300 my ($class, $data) = @_;
1301 return {} if !$data;
1302 return PVE::JSONSchema::parse_property_string($features_desc, $data);
1303 }
1304
1305 sub option_exists {
1306 my ($class, $name) = @_;
1307
1308 return defined($confdesc->{$name});
1309 }
1310 # END JSON config code
1311
1312 # takes a max memory value as KiB and returns an tuple with max and high values
1313 sub calculate_memory_constraints {
1314 my ($memory) = @_;
1315
1316 return if !defined($memory);
1317
1318 # cgroup memory usage is limited by the hard 'max' limit (OOM-killer enforced) and the soft
1319 # 'high' limit (cgroup processes get throttled and put under heavy reclaim pressure).
1320 my $memory_max = int($memory * 1024 * 1024);
1321 # Set the high to 1016/1024 (~99.2%) of the 'max' hard limit clamped to 128 MiB max, to scale
1322 # it for the lower range while having a decent 2^x based rest for 2^y memory configs.
1323 my $memory_high = $memory >= 16 * 1024 ? int(($memory - 128) * 1024 * 1024) : int($memory * 1024 * 1016);
1324
1325 return ($memory_max, $memory_high);
1326 }
1327
1328 my $LXC_FASTPLUG_OPTIONS= {
1329 'description' => 1,
1330 'onboot' => 1,
1331 'startup' => 1,
1332 'protection' => 1,
1333 'hostname' => 1,
1334 'hookscript' => 1,
1335 'cores' => 1,
1336 'tags' => 1,
1337 'lock' => 1,
1338 };
1339
1340 sub vmconfig_hotplug_pending {
1341 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1342
1343 my $pid = PVE::LXC::find_lxc_pid($vmid);
1344 my $rootdir = "/proc/$pid/root";
1345
1346 my $add_hotplug_error = sub {
1347 my ($opt, $msg) = @_;
1348 $errors->{$opt} = "unable to hotplug $opt: $msg";
1349 };
1350
1351 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
1352 next if $selection && !$selection->{$opt};
1353 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1354 $conf->{$opt} = delete $conf->{pending}->{$opt};
1355 }
1356 }
1357
1358 my $cgroup = PVE::LXC::CGroup->new($vmid);
1359
1360 # There's no separate swap size to configure, there's memory and "total"
1361 # memory (iow. memory+swap). This means we have to change them together.
1362 my $hotplug_memory_done;
1363 my $hotplug_memory = sub {
1364 my ($new_memory, $new_swap) = @_;
1365
1366 ($new_memory, my $new_memory_high) = calculate_memory_constraints($new_memory);
1367 $new_swap = int($new_swap * 1024 * 1024) if defined($new_swap);
1368 $cgroup->change_memory_limit($new_memory, $new_swap, $new_memory_high);
1369
1370 $hotplug_memory_done = 1;
1371 };
1372
1373 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1374 # FIXME: $force deletion is not implemented for CTs
1375 foreach my $opt (sort keys %$pending_delete_hash) {
1376 next if $selection && !$selection->{$opt};
1377 eval {
1378 if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
1379 # pass
1380 } elsif ($opt =~ m/^unused(\d+)$/) {
1381 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1382 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1383 } elsif ($opt eq 'swap') {
1384 $hotplug_memory->(undef, 0);
1385 } elsif ($opt eq 'cpulimit') {
1386 $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
1387 } elsif ($opt eq 'cpuunits') {
1388 $cgroup->change_cpu_shares(undef);
1389 } elsif ($opt =~ m/^net(\d)$/) {
1390 my $netid = $1;
1391 PVE::Network::veth_delete("veth${vmid}i$netid");
1392 if ($have_sdn) {
1393 my $net = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
1394 print "delete ips from $opt\n";
1395 eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{hwaddr}, $conf->{hostname}) };
1396 warn $@ if $@;
1397 }
1398 } else {
1399 die "skip\n"; # skip non-hotpluggable opts
1400 }
1401 };
1402 if (my $err = $@) {
1403 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1404 } else {
1405 delete $conf->{$opt};
1406 $class->remove_from_pending_delete($conf, $opt);
1407 }
1408 }
1409
1410 foreach my $opt (sort keys %{$conf->{pending}}) {
1411 next if $opt eq 'delete'; # just to be sure
1412 next if $selection && !$selection->{$opt};
1413 my $value = $conf->{pending}->{$opt};
1414 eval {
1415 if ($opt eq 'cpulimit') {
1416 my $quota = 100000 * $value;
1417 $cgroup->change_cpu_quota(int(100000 * $value), 100000);
1418 } elsif ($opt eq 'cpuunits') {
1419 $cgroup->change_cpu_shares($value);
1420 } elsif ($opt =~ m/^net(\d+)$/) {
1421 my $netid = $1;
1422 my $net = $class->parse_lxc_network($value);
1423 $value = $class->print_lxc_network($net);
1424 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1425 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1426 if (!$hotplug_memory_done) { # don't call twice if both opts are passed
1427 $hotplug_memory->($conf->{pending}->{memory}, $conf->{pending}->{swap});
1428 }
1429 } elsif ($opt =~ m/^mp(\d+)$/) {
1430 if (exists($conf->{$opt})) {
1431 die "skip\n"; # don't try to hotplug over existing mp
1432 }
1433
1434 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
1435 # apply_pending_mountpoint modifies the value if it creates a new disk
1436 $value = $conf->{pending}->{$opt};
1437 } else {
1438 die "skip\n"; # skip non-hotpluggable
1439 }
1440 };
1441 if (my $err = $@) {
1442 $add_hotplug_error->($opt, $err) if $err ne "skip\n";
1443 } else {
1444 $conf->{$opt} = $value;
1445 delete $conf->{pending}->{$opt};
1446 }
1447 }
1448 }
1449
1450 sub vmconfig_apply_pending {
1451 my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
1452
1453 my $add_apply_error = sub {
1454 my ($opt, $msg) = @_;
1455 my $err_msg = "unable to apply pending change $opt : $msg";
1456 $errors->{$opt} = $err_msg;
1457 warn $err_msg;
1458 };
1459
1460 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
1461 # FIXME: $force deletion is not implemented for CTs
1462 foreach my $opt (sort keys %$pending_delete_hash) {
1463 next if $selection && !$selection->{$opt};
1464 eval {
1465 if ($opt =~ m/^mp(\d+)$/) {
1466 my $mp = $class->parse_volume($opt, $conf->{$opt});
1467 if ($mp->{type} eq 'volume') {
1468 $class->add_unused_volume($conf, $mp->{volume})
1469 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1470 }
1471 } elsif ($opt =~ m/^unused(\d+)$/) {
1472 PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
1473 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1474 } elsif ($opt =~ m/^net(\d+)$/) {
1475 if ($have_sdn) {
1476 my $net = $class->parse_lxc_network($conf->{$opt});
1477 eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{hwaddr}, $conf->{hostname}) };
1478 warn $@ if $@;
1479 }
1480 }
1481 };
1482 if (my $err = $@) {
1483 $add_apply_error->($opt, $err);
1484 } else {
1485 delete $conf->{$opt};
1486 $class->remove_from_pending_delete($conf, $opt);
1487 }
1488 }
1489
1490 $class->cleanup_pending($conf);
1491
1492 foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
1493 next if $opt eq 'delete'; # just to be sure
1494 next if $selection && !$selection->{$opt};
1495 eval {
1496 if ($opt =~ m/^mp(\d+)$/) {
1497 $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 0);
1498 } elsif ($opt =~ m/^net(\d+)$/) {
1499 my $netid = $1;
1500 my $net = $class->parse_lxc_network($conf->{pending}->{$opt});
1501 $conf->{pending}->{$opt} = $class->print_lxc_network($net);
1502 if ($have_sdn) {
1503 if ($conf->{$opt}) {
1504 my $old_net = $class->parse_lxc_network($conf->{$opt});
1505 if ($old_net->{bridge} ne $net->{bridge} || $old_net->{hwaddr} ne $net->{hwaddr}) {
1506 PVE::Network::SDN::Vnets::del_ips_from_mac($old_net->{bridge}, $old_net->{hwaddr}, $conf->{name});
1507 PVE::Network::SDN::Vnets::add_next_free_cidr($net->{bridge}, $conf->{hostname}, $net->{hwaddr}, $vmid, undef, 1);
1508 }
1509 } else {
1510 PVE::Network::SDN::Vnets::add_next_free_cidr($net->{bridge}, $conf->{hostname}, $net->{hwaddr}, $vmid, undef, 1);
1511 }
1512 }
1513 }
1514 };
1515 if (my $err = $@) {
1516 $add_apply_error->($opt, $err);
1517 } else {
1518 $conf->{$opt} = delete $conf->{pending}->{$opt};
1519 }
1520 }
1521 }
1522
1523 my $rescan_volume = sub {
1524 my ($storecfg, $mp) = @_;
1525 eval {
1526 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5);
1527 };
1528 warn "Could not rescan volume size - $@\n" if $@;
1529 };
1530
1531 sub apply_pending_mountpoint {
1532 my ($class, $vmid, $conf, $opt, $storecfg, $running) = @_;
1533
1534 my $mp = $class->parse_volume($opt, $conf->{pending}->{$opt});
1535 my $old = $conf->{$opt};
1536 if ($mp->{type} eq 'volume' && $mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) {
1537 my $original_value = $conf->{pending}->{$opt};
1538 my $vollist = PVE::LXC::create_disks(
1539 $storecfg,
1540 $vmid,
1541 { $opt => $original_value },
1542 $conf,
1543 1,
1544 );
1545 if ($running) {
1546 # Re-parse mount point:
1547 my $mp = $class->parse_volume($opt, $conf->{pending}->{$opt});
1548 eval {
1549 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
1550 };
1551 my $err = $@;
1552 if ($err) {
1553 PVE::LXC::destroy_disks($storecfg, $vollist);
1554 # The pending-changes code collects errors but keeps on looping through further
1555 # pending changes, so unroll the change in $conf as well if destroy_disks()
1556 # didn't die().
1557 $conf->{pending}->{$opt} = $original_value;
1558 die $err;
1559 }
1560 }
1561 } else {
1562 die "skip\n" if $running && defined($old); # TODO: "changing" mount points?
1563 $rescan_volume->($storecfg, $mp) if $mp->{type} eq 'volume';
1564 if ($running) {
1565 PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg);
1566 }
1567 $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp);
1568 }
1569
1570 if (defined($old)) {
1571 my $mp = $class->parse_volume($opt, $old);
1572 if ($mp->{type} eq 'volume') {
1573 $class->add_unused_volume($conf, $mp->{volume})
1574 if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
1575 }
1576 }
1577 }
1578
1579 sub classify_mountpoint {
1580 my ($class, $vol) = @_;
1581 if ($vol =~ m!^/!) {
1582 return 'device' if $vol =~ m!^/dev/!;
1583 return 'bind';
1584 }
1585 return 'volume';
1586 }
1587
1588 my $__is_volume_in_use = sub {
1589 my ($class, $config, $volid) = @_;
1590 my $used = 0;
1591
1592 $class->foreach_volume($config, sub {
1593 my ($ms, $mountpoint) = @_;
1594 return if $used;
1595 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1596 });
1597
1598 return $used;
1599 };
1600
1601 sub is_volume_in_use_by_snapshots {
1602 my ($class, $config, $volid) = @_;
1603
1604 if (my $snapshots = $config->{snapshots}) {
1605 foreach my $snap (keys %$snapshots) {
1606 return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
1607 }
1608 }
1609
1610 return 0;
1611 }
1612
1613 sub is_volume_in_use {
1614 my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
1615 return 1 if $__is_volume_in_use->($class, $config, $volid);
1616 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1617 return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending}, $volid);
1618 return 0;
1619 }
1620
1621 sub has_dev_console {
1622 my ($class, $conf) = @_;
1623
1624 return !(defined($conf->{console}) && !$conf->{console});
1625 }
1626
1627 sub has_lxc_entry {
1628 my ($class, $conf, $keyname) = @_;
1629
1630 if (my $lxcconf = $conf->{lxc}) {
1631 foreach my $entry (@$lxcconf) {
1632 my ($key, undef) = @$entry;
1633 return 1 if $key eq $keyname;
1634 }
1635 }
1636
1637 return 0;
1638 }
1639
1640 sub get_tty_count {
1641 my ($class, $conf) = @_;
1642
1643 return $conf->{tty} // $confdesc->{tty}->{default};
1644 }
1645
1646 sub get_cmode {
1647 my ($class, $conf) = @_;
1648
1649 return $conf->{cmode} // $confdesc->{cmode}->{default};
1650 }
1651
1652 sub valid_volume_keys {
1653 my ($class, $reverse) = @_;
1654
1655 my @names = ('rootfs');
1656
1657 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1658 push @names, "mp$i";
1659 }
1660
1661 return $reverse ? reverse @names : @names;
1662 }
1663
1664 sub valid_volume_keys_with_unused {
1665 my ($class, $reverse) = @_;
1666 my @names = $class->valid_volume_keys();
1667 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
1668 push @names, "unused$i";
1669 }
1670 return $reverse ? reverse @names : @names;
1671 }
1672
1673 sub get_vm_volumes {
1674 my ($class, $conf, $excludes) = @_;
1675
1676 my $vollist = [];
1677
1678 $class->foreach_volume($conf, sub {
1679 my ($ms, $mountpoint) = @_;
1680
1681 return if $excludes && $ms eq $excludes;
1682
1683 my $volid = $mountpoint->{volume};
1684 return if !$volid || $mountpoint->{type} ne 'volume';
1685
1686 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1687 return if !$sid;
1688
1689 push @$vollist, $volid;
1690 });
1691
1692 return $vollist;
1693 }
1694
1695 sub get_replicatable_volumes {
1696 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
1697
1698 my $volhash = {};
1699
1700 my $test_volid = sub {
1701 my ($volid, $mountpoint) = @_;
1702
1703 return if !$volid;
1704
1705 my $mptype = $mountpoint->{type};
1706 my $replicate = $mountpoint->{replicate} // 1;
1707
1708 if ($mptype ne 'volume') {
1709 # skip bindmounts if replicate = 0 even for cleanup,
1710 # since bind mounts could not have been replicated ever
1711 return if !$replicate;
1712 die "unable to replicate mountpoint type '$mptype'\n";
1713 }
1714
1715 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1716 return if !$storeid;
1717
1718 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
1719 return if $scfg->{shared};
1720
1721 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1722 return if !$owner || ($owner != $vmid);
1723
1724 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1725
1726 return if !$cleanup && !$replicate;
1727
1728 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
1729 return if $cleanup || $noerr;
1730 die "missing replicate feature on volume '$volid'\n";
1731 }
1732
1733 $volhash->{$volid} = 1;
1734 };
1735
1736 $class->foreach_volume($conf, sub {
1737 my ($ms, $mountpoint) = @_;
1738 $test_volid->($mountpoint->{volume}, $mountpoint);
1739 });
1740
1741 foreach my $snapname (keys %{$conf->{snapshots}}) {
1742 my $snap = $conf->{snapshots}->{$snapname};
1743 $class->foreach_volume($snap, sub {
1744 my ($ms, $mountpoint) = @_;
1745 $test_volid->($mountpoint->{volume}, $mountpoint);
1746 });
1747 }
1748
1749 # add 'unusedX' volumes to volhash
1750 foreach my $key (keys %$conf) {
1751 if ($key =~ m/^unused/) {
1752 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1753 }
1754 }
1755
1756 return $volhash;
1757 }
1758
1759 sub get_backup_volumes {
1760 my ($class, $conf) = @_;
1761
1762 my $return_volumes = [];
1763
1764 my $test_mountpoint = sub {
1765 my ($key, $volume) = @_;
1766
1767 my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
1768
1769 push @$return_volumes, {
1770 key => $key,
1771 included => $included,
1772 reason => $reason,
1773 volume_config => $volume,
1774 };
1775 };
1776
1777 PVE::LXC::Config->foreach_volume($conf, $test_mountpoint);
1778
1779 return $return_volumes;
1780 }
1781
1782 sub get_derived_property {
1783 my ($class, $conf, $name) = @_;
1784
1785 if ($name eq 'max-cpu') {
1786 return $conf->{cpulimit} || $conf->{cores} || 0;
1787 } elsif ($name eq 'max-memory') {
1788 return ($conf->{memory} || 512) * 1024 * 1024;
1789 } else {
1790 die "unknown derived property - $name\n";
1791 }
1792 }
1793
1794 1;