]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
config: whitelist new apparmor related config keys
[pve-container.git] / src / PVE / LXC / Config.pm
CommitLineData
67afe46e
FG
1package PVE::LXC::Config;
2
3use strict;
4use warnings;
5
6use PVE::AbstractConfig;
7use PVE::Cluster qw(cfs_register_file);
8use PVE::INotify;
9use PVE::JSONSchema qw(get_standard_option);
10use PVE::Tools;
11
12use base qw(PVE::AbstractConfig);
13
14my $nodename = PVE::INotify::nodename();
15my $lock_handles = {};
16my $lockdir = "/run/lock/lxc";
17mkdir $lockdir;
18mkdir "/etc/pve/nodes/$nodename/lxc";
717f70b7 19my $MAX_MOUNT_POINTS = 256;
67afe46e
FG
20my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
21
22# BEGIN implemented abstract methods from PVE::AbstractConfig
23
24sub guest_type {
25 return "CT";
26}
27
4518000b
FG
28sub __config_max_unused_disks {
29 my ($class) = @_;
30
31 return $MAX_UNUSED_DISKS;
32}
33
67afe46e
FG
34sub config_file_lock {
35 my ($class, $vmid) = @_;
36
37 return "$lockdir/pve-config-${vmid}.lock";
38}
39
40sub cfs_config_path {
41 my ($class, $vmid, $node) = @_;
42
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
45}
46
1a8269bc 47sub mountpoint_backup_enabled {
ec7f0f09 48 my ($class, $mp_key, $mountpoint) = @_;
1a8269bc
DM
49
50 return 1 if $mp_key eq 'rootfs';
51
f59c9670
FG
52 return 0 if $mountpoint->{type} ne 'volume';
53
1a8269bc
DM
54 return 1 if $mountpoint->{backup};
55
56 return 0;
57}
58
4518000b
FG
59sub has_feature {
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
61 my $err;
62
d250604f 63 $class->foreach_mountpoint($conf, sub {
4518000b
FG
64 my ($ms, $mountpoint) = @_;
65
66 return if $err; # skip further test
ec7f0f09 67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
4518000b
FG
68
69 $err = 1
70 if !PVE::Storage::volume_has_feature($storecfg, $feature,
71 $mountpoint->{volume},
72 $snapname, $running);
73 });
74
75 return $err ? 0 : 1;
76}
77
78sub __snapshot_save_vmstate {
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
81}
82
83sub __snapshot_check_running {
84 my ($class, $vmid) = @_;
85 return PVE::LXC::check_running($vmid);
86}
87
88sub __snapshot_check_freeze_needed {
89 my ($class, $vmid, $config, $save_vmstate) = @_;
90
91 my $ret = $class->__snapshot_check_running($vmid);
92 return ($ret, $ret);
93}
94
95sub __snapshot_freeze {
96 my ($class, $vmid, $unfreeze) = @_;
97
98 if ($unfreeze) {
99 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
100 warn $@ if $@;
101 } else {
102 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
103 PVE::LXC::sync_container_namespace($vmid);
104 }
105}
106
107sub __snapshot_create_vol_snapshot {
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
109
110 my $storecfg = PVE::Storage::config();
111
1a8269bc 112 return if $snapname eq 'vzdump' &&
ec7f0f09 113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
1a8269bc 114
4518000b
FG
115 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
116}
117
118sub __snapshot_delete_remove_drive {
119 my ($class, $snap, $remove_drive) = @_;
120
121 if ($remove_drive eq 'vmstate') {
122 die "implement me - saving vmstate\n";
123 } else {
124 my $value = $snap->{$remove_drive};
1b4cf758 125 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
4518000b 126 delete $snap->{$remove_drive};
d103721f
FG
127
128 $class->add_unused_volume($snap, $mountpoint->{volume})
129 if ($mountpoint->{type} eq 'volume');
4518000b
FG
130 }
131}
132
133sub __snapshot_delete_vmstate_file {
134 my ($class, $snap, $force) = @_;
135
136 die "implement me - saving vmstate\n";
137}
138
139sub __snapshot_delete_vol_snapshot {
a8e9a4ea 140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
4518000b 141
d103721f
FG
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
144
4518000b
FG
145 my $storecfg = PVE::Storage::config();
146 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
a8e9a4ea 147 push @$unused, $mountpoint->{volume};
4518000b
FG
148}
149
150sub __snapshot_rollback_vol_possible {
151 my ($class, $mountpoint, $snapname) = @_;
152
153 my $storecfg = PVE::Storage::config();
154 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
155}
156
157sub __snapshot_rollback_vol_rollback {
158 my ($class, $mountpoint, $snapname) = @_;
159
160 my $storecfg = PVE::Storage::config();
161 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
162}
163
164sub __snapshot_rollback_vm_stop {
165 my ($class, $vmid) = @_;
166
b1bad293 167 PVE::LXC::vm_stop($vmid, 1)
4518000b
FG
168 if $class->__snapshot_check_running($vmid);
169}
170
171sub __snapshot_rollback_vm_start {
67779c3c 172 my ($class, $vmid, $vmstate, $data);
4518000b
FG
173
174 die "implement me - save vmstate\n";
175}
176
a8656869
FG
177sub __snapshot_rollback_get_unused {
178 my ($class, $conf, $snap) = @_;
179
180 my $unused = [];
181
182 $class->__snapshot_foreach_volume($conf, sub {
183 my ($vs, $volume) = @_;
184
185 return if $volume->{type} ne 'volume';
186
187 my $found = 0;
188 my $volid = $volume->{volume};
189
190 $class->__snapshot_foreach_volume($snap, sub {
191 my ($ms, $mountpoint) = @_;
192
193 return if $found;
194 return if ($mountpoint->{type} ne 'volume');
195
196 $found = 1
197 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
198 });
199
200 push @$unused, $volid if !$found;
201 });
202
203 return $unused;
204}
205
4518000b
FG
206sub __snapshot_foreach_volume {
207 my ($class, $conf, $func) = @_;
208
d250604f 209 $class->foreach_mountpoint($conf, $func);
4518000b
FG
210}
211
67afe46e
FG
212# END implemented abstract methods from PVE::AbstractConfig
213
1b4cf758
FG
214# BEGIN JSON config code
215
216cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
217
218my $rootfs_desc = {
219 volume => {
220 type => 'string',
221 default_key => 1,
222 format => 'pve-lxc-mp-string',
223 format_description => 'volume',
224 description => 'Volume, device or directory to mount into the container.',
225 },
1b4cf758
FG
226 size => {
227 type => 'string',
228 format => 'disk-size',
229 format_description => 'DiskSize',
230 description => 'Volume size (read only value).',
231 optional => 1,
232 },
233 acl => {
234 type => 'boolean',
1b4cf758
FG
235 description => 'Explicitly enable or disable ACL support.',
236 optional => 1,
237 },
238 ro => {
239 type => 'boolean',
235dbdf3 240 description => 'Read-only mount point',
1b4cf758
FG
241 optional => 1,
242 },
243 quota => {
244 type => 'boolean',
1b4cf758
FG
245 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
246 optional => 1,
247 },
76ec0820 248 replicate => {
f8aa3d35
WL
249 type => 'boolean',
250 description => 'Will include this volume to a storage replica job.',
251 optional => 1,
252 default => 1,
253 },
552e168f
FG
254 shared => {
255 type => 'boolean',
256 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
257 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!",
258 optional => 1,
259 default => 0,
260 },
1b4cf758
FG
261};
262
263PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
264 type => 'string', format => $rootfs_desc,
265 description => "Use volume as container root.",
266 optional => 1,
267});
268
269PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
270 description => "The name of the snapshot.",
271 type => 'string', format => 'pve-configid',
272 maxLength => 40,
273});
274
275my $confdesc = {
276 lock => {
277 optional => 1,
278 type => 'string',
279 description => "Lock/unlock the VM.",
14c9b535 280 enum => [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
1b4cf758
FG
281 },
282 onboot => {
283 optional => 1,
284 type => 'boolean',
285 description => "Specifies whether a VM will be started during system bootup.",
286 default => 0,
287 },
288 startup => get_standard_option('pve-startup-order'),
289 template => {
290 optional => 1,
291 type => 'boolean',
292 description => "Enable/disable Template.",
293 default => 0,
294 },
295 arch => {
296 optional => 1,
297 type => 'string',
e1d54a38 298 enum => ['amd64', 'i386', 'arm64', 'armhf'],
1b4cf758
FG
299 description => "OS architecture type.",
300 default => 'amd64',
301 },
302 ostype => {
303 optional => 1,
304 type => 'string',
ed027b58 305 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
1b4cf758
FG
306 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.",
307 },
308 console => {
309 optional => 1,
310 type => 'boolean',
311 description => "Attach a console device (/dev/console) to the container.",
312 default => 1,
313 },
314 tty => {
315 optional => 1,
316 type => 'integer',
317 description => "Specify the number of tty available to the container",
318 minimum => 0,
319 maximum => 6,
320 default => 2,
321 },
f2357408
DM
322 cores => {
323 optional => 1,
324 type => 'integer',
325 description => "The number of cores assigned to the container. A container can use all available cores by default.",
326 minimum => 1,
327 maximum => 128,
328 },
1b4cf758
FG
329 cpulimit => {
330 optional => 1,
331 type => 'number',
064529c3 332 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
FG
333 minimum => 0,
334 maximum => 128,
335 default => 0,
336 },
337 cpuunits => {
338 optional => 1,
339 type => 'integer',
340 description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
341 minimum => 0,
342 maximum => 500000,
343 default => 1024,
344 },
345 memory => {
346 optional => 1,
347 type => 'integer',
348 description => "Amount of RAM for the VM in MB.",
349 minimum => 16,
350 default => 512,
351 },
352 swap => {
353 optional => 1,
354 type => 'integer',
355 description => "Amount of SWAP for the VM in MB.",
356 minimum => 0,
357 default => 512,
358 },
359 hostname => {
360 optional => 1,
361 description => "Set a host name for the container.",
362 type => 'string', format => 'dns-name',
363 maxLength => 255,
364 },
365 description => {
366 optional => 1,
367 type => 'string',
a069f163 368 description => "Container description. Only used on the configuration web interface.",
1b4cf758
FG
369 },
370 searchdomain => {
371 optional => 1,
372 type => 'string', format => 'dns-name-list',
373 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
374 },
375 nameserver => {
376 optional => 1,
377 type => 'string', format => 'address-list',
378 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.",
379 },
380 rootfs => get_standard_option('pve-ct-rootfs'),
381 parent => {
382 optional => 1,
383 type => 'string', format => 'pve-configid',
384 maxLength => 40,
385 description => "Parent snapshot name. This is used internally, and should not be modified.",
386 },
387 snaptime => {
388 optional => 1,
389 description => "Timestamp for snapshots.",
390 type => 'integer',
391 minimum => 0,
392 },
393 cmode => {
394 optional => 1,
395 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).",
396 type => 'string',
397 enum => ['shell', 'console', 'tty'],
398 default => 'tty',
399 },
400 protection => {
401 optional => 1,
402 type => 'boolean',
403 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
404 default => 0,
405 },
406 unprivileged => {
407 optional => 1,
408 type => 'boolean',
409 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
410 default => 0,
411 },
412};
413
414my $valid_lxc_conf_keys = {
108c6cab
WB
415 'lxc.apparmor.profile' => 1,
416 'lxc.apparmor.allow_incomplete' => 1,
d494e03c
WB
417 'lxc.apparmor.allow_nesting' => 1,
418 'lxc.apparmor.raw' => 1,
108c6cab 419 'lxc.selinux.context' => 1,
1b4cf758
FG
420 'lxc.include' => 1,
421 'lxc.arch' => 1,
108c6cab
WB
422 'lxc.uts.name' => 1,
423 'lxc.signal.halt' => 1,
424 'lxc.signal.reboot' => 1,
425 'lxc.signal.stop' => 1,
426 'lxc.init.cmd' => 1,
427 'lxc.pty.max' => 1,
1b4cf758 428 'lxc.console.logfile' => 1,
108c6cab
WB
429 'lxc.console.path' => 1,
430 'lxc.tty.max' => 1,
431 'lxc.devtty.dir' => 1,
1b4cf758
FG
432 'lxc.hook.autodev' => 1,
433 'lxc.autodev' => 1,
434 'lxc.kmsg' => 1,
108c6cab 435 'lxc.mount.fstab' => 1,
1b4cf758
FG
436 'lxc.mount.entry' => 1,
437 'lxc.mount.auto' => 1,
108c6cab 438 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
1b4cf758
FG
439 'lxc.rootfs.mount' => 1,
440 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
235dbdf3 441 ', please use mount point options in the "rootfs" key',
1b4cf758 442 # lxc.cgroup.*
108c6cab 443 # lxc.prlimit.*
1b4cf758
FG
444 'lxc.cap.drop' => 1,
445 'lxc.cap.keep' => 1,
108c6cab
WB
446 'lxc.seccomp.profile' => 1,
447 'lxc.idmap' => 1,
1b4cf758
FG
448 'lxc.hook.pre-start' => 1,
449 'lxc.hook.pre-mount' => 1,
450 'lxc.hook.mount' => 1,
451 'lxc.hook.start' => 1,
452 'lxc.hook.stop' => 1,
453 'lxc.hook.post-stop' => 1,
454 'lxc.hook.clone' => 1,
455 'lxc.hook.destroy' => 1,
108c6cab
WB
456 'lxc.log.level' => 1,
457 'lxc.log.file' => 1,
1b4cf758
FG
458 'lxc.start.auto' => 1,
459 'lxc.start.delay' => 1,
460 'lxc.start.order' => 1,
461 'lxc.group' => 1,
462 'lxc.environment' => 1,
463};
464
108c6cab
WB
465my $deprecated_lxc_conf_keys = {
466 # Deprecated (removed with lxc 3.0):
467 'lxc.aa_profile' => 'lxc.apparmor.profile',
468 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
469 'lxc.console' => 'lxc.console.path',
470 'lxc.devttydir' => 'lxc.tty.dir',
471 'lxc.haltsignal' => 'lxc.signal.halt',
472 'lxc.rebootsignal' => 'lxc.signal.reboot',
473 'lxc.stopsignal' => 'lxc.signal.stop',
474 'lxc.id_map' => 'lxc.idmap',
475 'lxc.init_cmd' => 'lxc.init.cmd',
476 'lxc.loglevel' => 'lxc.log.level',
477 'lxc.logfile' => 'lxc.log.file',
478 'lxc.mount' => 'lxc.mount.fstab',
479 'lxc.network.type' => 'lxc.net.INDEX.type',
480 'lxc.network.flags' => 'lxc.net.INDEX.flags',
481 'lxc.network.link' => 'lxc.net.INDEX.link',
482 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
483 'lxc.network.name' => 'lxc.net.INDEX.name',
484 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
485 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
486 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
487 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
488 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
489 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
490 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
491 'lxc.pts' => 'lxc.pty.max',
492 'lxc.se_context' => 'lxc.selinux.context',
493 'lxc.seccomp' => 'lxc.seccomp.profile',
494 'lxc.tty' => 'lxc.tty.max',
495 'lxc.utsname' => 'lxc.uts.name',
496};
497
498sub is_valid_lxc_conf_key {
499 my ($vmid, $key) = @_;
500 if ($key =~ /^lxc\.limit\./) {
501 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
502 return 1;
503 }
504 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
505 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
506 return 1;
507 }
508 my $validity = $valid_lxc_conf_keys->{$key};
509 return $validity if defined($validity);
510 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
511 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
512 || $key =~ /^lxc\.net\./; # allow custom network definitions
513 return 0;
514}
515
5e5915c5 516our $netconf_desc = {
1b4cf758
FG
517 type => {
518 type => 'string',
519 optional => 1,
520 description => "Network interface type.",
521 enum => [qw(veth)],
522 },
523 name => {
524 type => 'string',
a069f163
DM
525 format_description => 'string',
526 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
527 pattern => '[-_.\w\d]+',
528 },
529 bridge => {
530 type => 'string',
a069f163 531 format_description => 'bridge',
1b4cf758
FG
532 description => 'Bridge to attach the network device to.',
533 pattern => '[-_.\w\d]+',
534 optional => 1,
535 },
536 hwaddr => {
537 type => 'string',
a069f163 538 format_description => "XX:XX:XX:XX:XX:XX",
e6f20294 539 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)',
1b4cf758
FG
540 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
541 optional => 1,
542 },
543 mtu => {
544 type => 'integer',
1b4cf758
FG
545 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
546 minimum => 64, # minimum ethernet frame is 64 bytes
547 optional => 1,
548 },
549 ip => {
550 type => 'string',
551 format => 'pve-ipv4-config',
718a67d6 552 format_description => '(IPv4/CIDR|dhcp|manual)',
1b4cf758
FG
553 description => 'IPv4 address in CIDR format.',
554 optional => 1,
555 },
556 gw => {
557 type => 'string',
558 format => 'ipv4',
559 format_description => 'GatewayIPv4',
560 description => 'Default gateway for IPv4 traffic.',
561 optional => 1,
562 },
563 ip6 => {
564 type => 'string',
565 format => 'pve-ipv6-config',
6ea7095c 566 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
1b4cf758
FG
567 description => 'IPv6 address in CIDR format.',
568 optional => 1,
569 },
570 gw6 => {
571 type => 'string',
572 format => 'ipv6',
573 format_description => 'GatewayIPv6',
574 description => 'Default gateway for IPv6 traffic.',
575 optional => 1,
576 },
577 firewall => {
578 type => 'boolean',
1b4cf758
FG
579 description => "Controls whether this interface's firewall rules should be used.",
580 optional => 1,
581 },
582 tag => {
583 type => 'integer',
6b202dd5
DM
584 minimum => 1,
585 maximum => 4094,
1b4cf758
FG
586 description => "VLAN tag for this interface.",
587 optional => 1,
588 },
589 trunks => {
590 type => 'string',
591 pattern => qr/\d+(?:;\d+)*/,
592 format_description => 'vlanid[;vlanid...]',
593 description => "VLAN ids to pass through the interface",
594 optional => 1,
595 },
380962c7
WB
596 rate => {
597 type => 'number',
598 format_description => 'mbps',
599 description => "Apply rate limiting to the interface",
600 optional => 1,
601 },
1b4cf758
FG
602};
603PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
604
605my $MAX_LXC_NETWORKS = 10;
606for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
607 $confdesc->{"net$i"} = {
608 optional => 1,
609 type => 'string', format => $netconf_desc,
610 description => "Specifies network interfaces for the container.",
611 };
612}
613
614PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
615sub verify_lxc_mp_string {
616 my ($mp, $noerr) = @_;
617
618 # do not allow:
619 # /./ or /../
620 # /. or /.. at the end
621 # ../ at the beginning
622
623 if($mp =~ m@/\.\.?/@ ||
624 $mp =~ m@/\.\.?$@ ||
625 $mp =~ m@^\.\./@) {
626 return undef if $noerr;
627 die "$mp contains illegal character sequences\n";
628 }
629 return $mp;
630}
631
632my $mp_desc = {
633 %$rootfs_desc,
84820d40
DM
634 backup => {
635 type => 'boolean',
235dbdf3
FG
636 description => 'Whether to include the mount point in backups.',
637 verbose_description => 'Whether to include the mount point in backups '.
638 '(only used for volume mount points).',
84820d40
DM
639 optional => 1,
640 },
1b4cf758
FG
641 mp => {
642 type => 'string',
643 format => 'pve-lxc-mp-string',
644 format_description => 'Path',
235dbdf3 645 description => 'Path to the mount point as seen from inside the container '.
52b6f941 646 '(must not contain symlinks).',
235dbdf3 647 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
52b6f941 648 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
649 },
650};
651PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
652
653my $unuseddesc = {
654 optional => 1,
655 type => 'string', format => 'pve-volume-id',
2928e616 656 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
1b4cf758
FG
657};
658
659for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
660 $confdesc->{"mp$i"} = {
661 optional => 1,
662 type => 'string', format => $mp_desc,
2928e616 663 description => "Use volume as container mount point.",
1b4cf758
FG
664 optional => 1,
665 };
666}
667
668for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
669 $confdesc->{"unused$i"} = $unuseddesc;
670}
671
672sub parse_pct_config {
673 my ($filename, $raw) = @_;
674
675 return undef if !defined($raw);
676
677 my $res = {
678 digest => Digest::SHA::sha1_hex($raw),
679 snapshots => {},
680 };
681
682 $filename =~ m|/lxc/(\d+).conf$|
683 || die "got strange filename '$filename'";
684
685 my $vmid = $1;
686
687 my $conf = $res;
688 my $descr = '';
689 my $section = '';
690
691 my @lines = split(/\n/, $raw);
692 foreach my $line (@lines) {
693 next if $line =~ m/^\s*$/;
694
695 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
696 $section = $1;
697 $conf->{description} = $descr if $descr;
698 $descr = '';
699 $conf = $res->{snapshots}->{$section} = {};
700 next;
701 }
702
703 if ($line =~ m/^\#(.*)\s*$/) {
704 $descr .= PVE::Tools::decode_text($1) . "\n";
705 next;
706 }
707
708 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
709 my $key = $1;
710 my $value = $3;
108c6cab
WB
711 my $validity = is_valid_lxc_conf_key($vmid, $key);
712 if ($validity eq 1) {
1b4cf758
FG
713 push @{$conf->{lxc}}, [$key, $value];
714 } elsif (my $errmsg = $validity) {
715 warn "vm $vmid - $key: $errmsg\n";
716 } else {
717 warn "vm $vmid - unable to parse config: $line\n";
718 }
719 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
720 $descr .= PVE::Tools::decode_text($2);
721 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
722 $conf->{snapstate} = $1;
723 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
724 my $key = $1;
725 my $value = $2;
726 eval { $value = PVE::LXC::Config->check_type($key, $value); };
727 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
728 $conf->{$key} = $value;
729 } else {
730 warn "vm $vmid - unable to parse config: $line\n";
731 }
732 }
733
734 $conf->{description} = $descr if $descr;
735
736 delete $res->{snapstate}; # just to be sure
737
738 return $res;
739}
740
741sub write_pct_config {
742 my ($filename, $conf) = @_;
743
744 delete $conf->{snapstate}; # just to be sure
745
746 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
747 my $used_volids = {};
748 foreach my $vid (@$volidlist) {
749 $used_volids->{$vid} = 1;
750 }
751
752 # remove 'unusedX' settings if the volume is still used
753 foreach my $key (keys %$conf) {
754 my $value = $conf->{$key};
755 if ($key =~ m/^unused/ && $used_volids->{$value}) {
756 delete $conf->{$key};
757 }
758 }
759
760 my $generate_raw_config = sub {
761 my ($conf) = @_;
762
763 my $raw = '';
764
765 # add description as comment to top of file
766 my $descr = $conf->{description} || '';
767 foreach my $cl (split(/\n/, $descr)) {
768 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
769 }
770
771 foreach my $key (sort keys %$conf) {
772 next if $key eq 'digest' || $key eq 'description' ||
773 $key eq 'pending' || $key eq 'snapshots' ||
774 $key eq 'snapname' || $key eq 'lxc';
775 my $value = $conf->{$key};
776 die "detected invalid newline inside property '$key'\n"
777 if $value =~ m/\n/;
778 $raw .= "$key: $value\n";
779 }
780
781 if (my $lxcconf = $conf->{lxc}) {
782 foreach my $entry (@$lxcconf) {
783 my ($k, $v) = @$entry;
784 $raw .= "$k: $v\n";
785 }
786 }
787
788 return $raw;
789 };
790
791 my $raw = &$generate_raw_config($conf);
792
793 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
794 $raw .= "\n[$snapname]\n";
795 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
796 }
797
798 return $raw;
799}
800
801sub update_pct_config {
802 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
803
804 my @nohotplug;
805
806 my $new_disks = 0;
807 my @deleted_volumes;
808
809 my $rootdir;
810 if ($running) {
811 my $pid = PVE::LXC::find_lxc_pid($vmid);
812 $rootdir = "/proc/$pid/root";
813 }
814
815 my $hotplug_error = sub {
816 if ($running) {
817 push @nohotplug, @_;
818 return 1;
819 } else {
820 return 0;
821 }
822 };
823
824 if (defined($delete)) {
825 foreach my $opt (@$delete) {
826 if (!exists($conf->{$opt})) {
a4a75420 827 # silently ignore
1b4cf758
FG
828 next;
829 }
830
e96a0ceb 831 if ($opt eq 'memory' || $opt eq 'rootfs') {
1b4cf758 832 die "unable to delete required option '$opt'\n";
e96a0ceb
DC
833 } elsif ($opt eq 'hostname') {
834 delete $conf->{$opt};
1b4cf758
FG
835 } elsif ($opt eq 'swap') {
836 delete $conf->{$opt};
837 PVE::LXC::write_cgroup_value("memory", $vmid,
838 "memory.memsw.limit_in_bytes", -1);
839 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
840 delete $conf->{$opt};
841 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
842 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
843 next if $hotplug_error->($opt);
844 delete $conf->{$opt};
f2357408
DM
845 } elsif ($opt eq 'cores') {
846 delete $conf->{$opt}; # rest is handled by pvestatd
d13b770f 847 } elsif ($opt eq 'cpulimit') {
213d70e6 848 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
d13b770f
WB
849 delete $conf->{$opt};
850 } elsif ($opt eq 'cpuunits') {
851 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
852 delete $conf->{$opt};
1b4cf758
FG
853 } elsif ($opt =~ m/^net(\d)$/) {
854 delete $conf->{$opt};
855 next if !$running;
856 my $netid = $1;
857 PVE::Network::veth_delete("veth${vmid}i$netid");
858 } elsif ($opt eq 'protection') {
859 delete $conf->{$opt};
860 } elsif ($opt =~ m/^unused(\d+)$/) {
861 next if $hotplug_error->($opt);
862 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
863 push @deleted_volumes, $conf->{$opt};
864 delete $conf->{$opt};
865 } elsif ($opt =~ m/^mp(\d+)$/) {
866 next if $hotplug_error->($opt);
867 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
868 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
869 delete $conf->{$opt};
870 if ($mp->{type} eq 'volume') {
871 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
872 }
873 } elsif ($opt eq 'unprivileged') {
874 die "unable to delete read-only option: '$opt'\n";
875 } else {
876 die "implement me (delete: $opt)"
877 }
878 PVE::LXC::Config->write_config($vmid, $conf) if $running;
879 }
880 }
881
882 # There's no separate swap size to configure, there's memory and "total"
883 # memory (iow. memory+swap). This means we have to change them together.
884 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
885 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
886 if (defined($wanted_memory) || defined($wanted_swap)) {
887
888 my $old_memory = ($conf->{memory} || 512);
889 my $old_swap = ($conf->{swap} || 0);
890
891 $wanted_memory //= $old_memory;
892 $wanted_swap //= $old_swap;
893
894 my $total = $wanted_memory + $wanted_swap;
895 if ($running) {
896 my $old_total = $old_memory + $old_swap;
897 if ($total > $old_total) {
898 PVE::LXC::write_cgroup_value("memory", $vmid,
899 "memory.memsw.limit_in_bytes",
900 int($total*1024*1024));
901 PVE::LXC::write_cgroup_value("memory", $vmid,
902 "memory.limit_in_bytes",
903 int($wanted_memory*1024*1024));
904 } else {
905 PVE::LXC::write_cgroup_value("memory", $vmid,
906 "memory.limit_in_bytes",
907 int($wanted_memory*1024*1024));
908 PVE::LXC::write_cgroup_value("memory", $vmid,
909 "memory.memsw.limit_in_bytes",
910 int($total*1024*1024));
911 }
912 }
913 $conf->{memory} = $wanted_memory;
914 $conf->{swap} = $wanted_swap;
915
916 PVE::LXC::Config->write_config($vmid, $conf) if $running;
917 }
918
f78c87a8
DM
919 my $storecfg = PVE::Storage::config();
920
1b4cf758 921 my $used_volids = {};
1b3213ae
FG
922 my $check_content_type = sub {
923 my ($mp) = @_;
924 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
f78c87a8 925 my $storage_config = PVE::Storage::storage_config($storecfg, $sid);
1b3213ae
FG
926 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
927 if !$storage_config->{content}->{rootdir};
928 };
1b4cf758 929
2718eec6
FG
930 my $rescan_volume = sub {
931 my ($mp) = @_;
932 eval {
933 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
934 if !defined($mp->{size});
935 };
936 warn "Could not rescan volume size - $@\n" if $@;
937 };
938
1b4cf758
FG
939 foreach my $opt (keys %$param) {
940 my $value = $param->{$opt};
941 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
f31bd6ae 942 if ($opt eq 'hostname' || $opt eq 'arch') {
1b4cf758
FG
943 $conf->{$opt} = $value;
944 } elsif ($opt eq 'onboot') {
945 $conf->{$opt} = $value ? 1 : 0;
946 } elsif ($opt eq 'startup') {
947 $conf->{$opt} = $value;
948 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
949 next if $hotplug_error->($opt);
950 $conf->{$opt} = $value;
951 } elsif ($opt eq 'nameserver') {
952 next if $hotplug_error->($opt);
953 my $list = PVE::LXC::verify_nameserver_list($value);
954 $conf->{$opt} = $list;
955 } elsif ($opt eq 'searchdomain') {
956 next if $hotplug_error->($opt);
957 my $list = PVE::LXC::verify_searchdomain_list($value);
958 $conf->{$opt} = $list;
f2357408
DM
959 } elsif ($opt eq 'cores') {
960 $conf->{$opt} = $value;# rest is handled by pvestatd
1b4cf758 961 } elsif ($opt eq 'cpulimit') {
115e5862
FG
962 if ($value == 0) {
963 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
964 } else {
965 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
966 }
1b4cf758
FG
967 $conf->{$opt} = $value;
968 } elsif ($opt eq 'cpuunits') {
969 $conf->{$opt} = $value;
970 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
971 } elsif ($opt eq 'description') {
d9c44e56 972 $conf->{$opt} = $value;
1b4cf758
FG
973 } elsif ($opt =~ m/^net(\d+)$/) {
974 my $netid = $1;
975 my $net = PVE::LXC::Config->parse_lxc_network($value);
976 if (!$running) {
977 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
978 } else {
979 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
980 }
981 } elsif ($opt eq 'protection') {
982 $conf->{$opt} = $value ? 1 : 0;
983 } elsif ($opt =~ m/^mp(\d+)$/) {
984 next if $hotplug_error->($opt);
985 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
986 my $old = $conf->{$opt};
56d7fc1b
FG
987 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
988 if ($mp->{type} eq 'volume') {
1b3213ae 989 &$check_content_type($mp);
56d7fc1b 990 $used_volids->{$mp->{volume}} = 1;
2718eec6
FG
991 &$rescan_volume($mp);
992 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
993 } else {
994 $conf->{$opt} = $value;
56d7fc1b 995 }
1b4cf758
FG
996 if (defined($old)) {
997 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
998 if ($mp->{type} eq 'volume') {
999 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1000 }
1001 }
1002 $new_disks = 1;
1b4cf758
FG
1003 } elsif ($opt eq 'rootfs') {
1004 next if $hotplug_error->($opt);
1005 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1006 my $old = $conf->{$opt};
56d7fc1b
FG
1007 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
1008 if ($mp->{type} eq 'volume') {
1b3213ae 1009 &$check_content_type($mp);
56d7fc1b 1010 $used_volids->{$mp->{volume}} = 1;
2718eec6
FG
1011 &$rescan_volume($mp);
1012 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
1013 } else {
1014 $conf->{$opt} = $value;
56d7fc1b 1015 }
1b4cf758
FG
1016 if (defined($old)) {
1017 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
1018 if ($mp->{type} eq 'volume') {
1019 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1020 }
1021 }
1d9369fd 1022 $new_disks = 1;
1b4cf758
FG
1023 } elsif ($opt eq 'unprivileged') {
1024 die "unable to modify read-only option: '$opt'\n";
1025 } elsif ($opt eq 'ostype') {
1026 next if $hotplug_error->($opt);
1027 $conf->{$opt} = $value;
1028 } else {
1029 die "implement me: $opt";
1030 }
dc692997 1031
76ec0820 1032 PVE::LXC::Config->write_config($vmid, $conf) if $running;
f8aa3d35
WL
1033 }
1034
1b4cf758
FG
1035 # Apply deletions and creations of new volumes
1036 if (@deleted_volumes) {
1037 my $storage_cfg = PVE::Storage::config();
1038 foreach my $volume (@deleted_volumes) {
1039 next if $used_volids->{$volume}; # could have been re-added, too
1040 # also check for references in snapshots
1041 next if $class->is_volume_in_use($conf, $volume, 1);
1042 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1043 }
1044 }
1045
1046 if ($new_disks) {
1047 my $storage_cfg = PVE::Storage::config();
1048 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
1049 }
1050
1051 # This should be the last thing we do here
1052 if ($running && scalar(@nohotplug)) {
1053 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1054 }
1055}
1056
1057sub check_type {
1058 my ($class, $key, $value) = @_;
1059
1060 die "unknown setting '$key'\n" if !$confdesc->{$key};
1061
1062 my $type = $confdesc->{$key}->{type};
1063
1064 if (!defined($value)) {
1065 die "got undefined value\n";
1066 }
1067
1068 if ($value =~ m/[\n\r]/) {
1069 die "property contains a line feed\n";
1070 }
1071
1072 if ($type eq 'boolean') {
1073 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1074 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1075 die "type check ('boolean') failed - got '$value'\n";
1076 } elsif ($type eq 'integer') {
1077 return int($1) if $value =~ m/^(\d+)$/;
1078 die "type check ('integer') failed - got '$value'\n";
1079 } elsif ($type eq 'number') {
1080 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1081 die "type check ('number') failed - got '$value'\n";
1082 } elsif ($type eq 'string') {
1083 if (my $fmt = $confdesc->{$key}->{format}) {
1084 PVE::JSONSchema::check_format($fmt, $value);
1085 return $value;
1086 }
1087 return $value;
1088 } else {
1089 die "internal error"
1090 }
1091}
1092
1093
1094# add JSON properties for create and set function
1095sub json_config_properties {
1096 my ($class, $prop) = @_;
1097
1098 foreach my $opt (keys %$confdesc) {
1099 next if $opt eq 'parent' || $opt eq 'snaptime';
1100 next if $prop->{$opt};
1101 $prop->{$opt} = $confdesc->{$opt};
1102 }
1103
1104 return $prop;
1105}
1106
1107sub __parse_ct_mountpoint_full {
1108 my ($class, $desc, $data, $noerr) = @_;
1109
1110 $data //= '';
1111
1112 my $res;
1113 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1114 if ($@) {
1115 return undef if $noerr;
1116 die $@;
1117 }
1118
1119 if (defined(my $size = $res->{size})) {
1120 $size = PVE::JSONSchema::parse_size($size);
1121 if (!defined($size)) {
1122 return undef if $noerr;
1123 die "invalid size: $size\n";
1124 }
1125 $res->{size} = $size;
1126 }
1127
1128 $res->{type} = $class->classify_mountpoint($res->{volume});
1129
1130 return $res;
1131};
1132
1133sub parse_ct_rootfs {
1134 my ($class, $data, $noerr) = @_;
1135
1136 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1137
1138 $res->{mp} = '/' if defined($res);
1139
1140 return $res;
1141}
1142
1143sub parse_ct_mountpoint {
1144 my ($class, $data, $noerr) = @_;
1145
1146 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1147}
1148
1149sub print_ct_mountpoint {
1150 my ($class, $info, $nomp) = @_;
1151 my $skip = [ 'type' ];
1152 push @$skip, 'mp' if $nomp;
1153 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1154}
1155
1156sub print_lxc_network {
1157 my ($class, $net) = @_;
1158 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1159}
1160
1161sub parse_lxc_network {
1162 my ($class, $data) = @_;
1163
1164 my $res = {};
1165
1166 return $res if !$data;
1167
1168 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1169
1170 $res->{type} = 'veth';
2f19133b
WB
1171 if (!$res->{hwaddr}) {
1172 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1173 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1174 }
1b4cf758
FG
1175
1176 return $res;
1177}
1178
1179sub option_exists {
1180 my ($class, $name) = @_;
1181
1182 return defined($confdesc->{$name});
1183}
1184# END JSON config code
1185
d250604f
FG
1186sub classify_mountpoint {
1187 my ($class, $vol) = @_;
1188 if ($vol =~ m!^/!) {
1189 return 'device' if $vol =~ m!^/dev/!;
1190 return 'bind';
1191 }
1192 return 'volume';
1193}
1194
72e6bc20
WB
1195my $is_volume_in_use = sub {
1196 my ($class, $config, $volid) = @_;
d250604f
FG
1197 my $used = 0;
1198
1199 $class->foreach_mountpoint($config, sub {
1200 my ($ms, $mountpoint) = @_;
1201 return if $used;
1202 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1203 });
1204
72e6bc20
WB
1205 return $used;
1206};
1207
1208sub is_volume_in_use_by_snapshots {
1209 my ($class, $config, $volid) = @_;
1210
1211 if (my $snapshots = $config->{snapshots}) {
d250604f 1212 foreach my $snap (keys %$snapshots) {
72e6bc20 1213 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
d250604f
FG
1214 }
1215 }
1216
72e6bc20
WB
1217 return 0;
1218};
1219
1220sub is_volume_in_use {
1221 my ($class, $config, $volid, $include_snapshots) = @_;
1222 return 1 if $is_volume_in_use->($class, $config, $volid);
1223 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1224 return 0;
d250604f
FG
1225}
1226
1227sub has_dev_console {
1228 my ($class, $conf) = @_;
1229
1230 return !(defined($conf->{console}) && !$conf->{console});
1231}
1232
be7942f0
DM
1233sub has_lxc_entry {
1234 my ($class, $conf, $keyname) = @_;
1235
1236 if (my $lxcconf = $conf->{lxc}) {
1237 foreach my $entry (@$lxcconf) {
1238 my ($key, undef) = @$entry;
1239 return 1 if $key eq $keyname;
1240 }
1241 }
1242
1243 return 0;
1244}
1245
1b4cf758
FG
1246sub get_tty_count {
1247 my ($class, $conf) = @_;
1248
1249 return $conf->{tty} // $confdesc->{tty}->{default};
1250}
1251
1252sub get_cmode {
1253 my ($class, $conf) = @_;
1254
1255 return $conf->{cmode} // $confdesc->{cmode}->{default};
1256}
1257
d250604f
FG
1258sub mountpoint_names {
1259 my ($class, $reverse) = @_;
1260
1261 my @names = ('rootfs');
1262
1263 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1264 push @names, "mp$i";
1265 }
1266
1267 return $reverse ? reverse @names : @names;
1268}
1269
1270sub foreach_mountpoint_full {
8915b450 1271 my ($class, $conf, $reverse, $func, @param) = @_;
d250604f 1272
717f70b7
FG
1273 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1274 foreach my $key (@$mps) {
d250604f 1275 my $value = $conf->{$key};
1b4cf758 1276 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
d250604f
FG
1277 next if !defined($mountpoint);
1278
8915b450 1279 &$func($key, $mountpoint, @param);
d250604f
FG
1280 }
1281}
1282
1283sub foreach_mountpoint {
8915b450 1284 my ($class, $conf, $func, @param) = @_;
d250604f 1285
8915b450 1286 $class->foreach_mountpoint_full($conf, 0, $func, @param);
d250604f
FG
1287}
1288
1289sub foreach_mountpoint_reverse {
8915b450 1290 my ($class, $conf, $func, @param) = @_;
d250604f 1291
8915b450 1292 $class->foreach_mountpoint_full($conf, 1, $func, @param);
d250604f
FG
1293}
1294
1295sub get_vm_volumes {
1296 my ($class, $conf, $excludes) = @_;
1297
1298 my $vollist = [];
1299
1300 $class->foreach_mountpoint($conf, sub {
1301 my ($ms, $mountpoint) = @_;
1302
1303 return if $excludes && $ms eq $excludes;
1304
1305 my $volid = $mountpoint->{volume};
1306 return if !$volid || $mountpoint->{type} ne 'volume';
1307
1308 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1309 return if !$sid;
1310
1311 push @$vollist, $volid;
1312 });
1313
1314 return $vollist;
1315}
1316
f78c87a8 1317sub get_replicatable_volumes {
70996986 1318 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
f78c87a8
DM
1319
1320 my $volhash = {};
1321
1322 my $test_volid = sub {
1323 my ($volid, $mountpoint) = @_;
1324
1325 return if !$volid;
1326
5cf90b0c 1327 my $mptype = $mountpoint->{type};
896cd762 1328 my $replicate = $mountpoint->{replicate} // 1;
d2a046b7
DC
1329
1330 if ($mptype ne 'volume') {
1331 # skip bindmounts if replicate = 0 even for cleanup,
1332 # since bind mounts could not have been replicated ever
1333 return if !$replicate;
1334 die "unable to replicate mountpoint type '$mptype'\n";
1335 }
5cf90b0c
DM
1336
1337 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1338 return if !$storeid;
1339
af21e699 1340 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
5cf90b0c
DM
1341 return if $scfg->{shared};
1342
1343 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1344 return if !$owner || ($owner != $vmid);
1345
1346 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1347
d2a046b7 1348 return if !$cleanup && !$replicate;
f78c87a8
DM
1349
1350 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
e65dce6b 1351 return if $cleanup || $noerr;
f78c87a8
DM
1352 die "missing replicate feature on volume '$volid'\n";
1353 }
1354
1355 $volhash->{$volid} = 1;
1356 };
1357
1358 $class->foreach_mountpoint($conf, sub {
1359 my ($ms, $mountpoint) = @_;
1360 $test_volid->($mountpoint->{volume}, $mountpoint);
1361 });
1362
1363 foreach my $snapname (keys %{$conf->{snapshots}}) {
1364 my $snap = $conf->{snapshots}->{$snapname};
1365 $class->foreach_mountpoint($snap, sub {
1366 my ($ms, $mountpoint) = @_;
1367 $test_volid->($mountpoint->{volume}, $mountpoint);
1368 });
1369 }
1370
b03664ae
DM
1371 # add 'unusedX' volumes to volhash
1372 foreach my $key (keys %$conf) {
1373 if ($key =~ m/^unused/) {
1374 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1375 }
1376 }
1377
f78c87a8
DM
1378 return $volhash;
1379}
1380
13811;