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