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