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