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