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