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