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