]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
fix #1897: bump MAX_MOUNT_POINTS to 256
[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 {
172 my ($class, $vmid, $vmstate, $forcemachine);
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.",
3c049a12 280 enum => [qw(mounted migrate backup snapshot rollback)],
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',
298 enum => ['amd64', 'i386'],
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,
417 'lxc.selinux.context' => 1,
1b4cf758
FG
418 'lxc.include' => 1,
419 'lxc.arch' => 1,
108c6cab
WB
420 'lxc.uts.name' => 1,
421 'lxc.signal.halt' => 1,
422 'lxc.signal.reboot' => 1,
423 'lxc.signal.stop' => 1,
424 'lxc.init.cmd' => 1,
425 'lxc.pty.max' => 1,
1b4cf758 426 'lxc.console.logfile' => 1,
108c6cab
WB
427 'lxc.console.path' => 1,
428 'lxc.tty.max' => 1,
429 'lxc.devtty.dir' => 1,
1b4cf758
FG
430 'lxc.hook.autodev' => 1,
431 'lxc.autodev' => 1,
432 'lxc.kmsg' => 1,
108c6cab 433 'lxc.mount.fstab' => 1,
1b4cf758
FG
434 'lxc.mount.entry' => 1,
435 'lxc.mount.auto' => 1,
108c6cab 436 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
1b4cf758
FG
437 'lxc.rootfs.mount' => 1,
438 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
235dbdf3 439 ', please use mount point options in the "rootfs" key',
1b4cf758 440 # lxc.cgroup.*
108c6cab 441 # lxc.prlimit.*
1b4cf758
FG
442 'lxc.cap.drop' => 1,
443 'lxc.cap.keep' => 1,
108c6cab
WB
444 'lxc.seccomp.profile' => 1,
445 'lxc.idmap' => 1,
1b4cf758
FG
446 'lxc.hook.pre-start' => 1,
447 'lxc.hook.pre-mount' => 1,
448 'lxc.hook.mount' => 1,
449 'lxc.hook.start' => 1,
450 'lxc.hook.stop' => 1,
451 'lxc.hook.post-stop' => 1,
452 'lxc.hook.clone' => 1,
453 'lxc.hook.destroy' => 1,
108c6cab
WB
454 'lxc.log.level' => 1,
455 'lxc.log.file' => 1,
1b4cf758
FG
456 'lxc.start.auto' => 1,
457 'lxc.start.delay' => 1,
458 'lxc.start.order' => 1,
459 'lxc.group' => 1,
460 'lxc.environment' => 1,
461};
462
108c6cab
WB
463my $deprecated_lxc_conf_keys = {
464 # Deprecated (removed with lxc 3.0):
465 'lxc.aa_profile' => 'lxc.apparmor.profile',
466 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
467 'lxc.console' => 'lxc.console.path',
468 'lxc.devttydir' => 'lxc.tty.dir',
469 'lxc.haltsignal' => 'lxc.signal.halt',
470 'lxc.rebootsignal' => 'lxc.signal.reboot',
471 'lxc.stopsignal' => 'lxc.signal.stop',
472 'lxc.id_map' => 'lxc.idmap',
473 'lxc.init_cmd' => 'lxc.init.cmd',
474 'lxc.loglevel' => 'lxc.log.level',
475 'lxc.logfile' => 'lxc.log.file',
476 'lxc.mount' => 'lxc.mount.fstab',
477 'lxc.network.type' => 'lxc.net.INDEX.type',
478 'lxc.network.flags' => 'lxc.net.INDEX.flags',
479 'lxc.network.link' => 'lxc.net.INDEX.link',
480 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
481 'lxc.network.name' => 'lxc.net.INDEX.name',
482 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
483 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
484 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
485 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
486 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
487 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
488 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
489 'lxc.pts' => 'lxc.pty.max',
490 'lxc.se_context' => 'lxc.selinux.context',
491 'lxc.seccomp' => 'lxc.seccomp.profile',
492 'lxc.tty' => 'lxc.tty.max',
493 'lxc.utsname' => 'lxc.uts.name',
494};
495
496sub is_valid_lxc_conf_key {
497 my ($vmid, $key) = @_;
498 if ($key =~ /^lxc\.limit\./) {
499 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
500 return 1;
501 }
502 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
503 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
504 return 1;
505 }
506 my $validity = $valid_lxc_conf_keys->{$key};
507 return $validity if defined($validity);
508 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
509 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
510 || $key =~ /^lxc\.net\./; # allow custom network definitions
511 return 0;
512}
513
5e5915c5 514our $netconf_desc = {
1b4cf758
FG
515 type => {
516 type => 'string',
517 optional => 1,
518 description => "Network interface type.",
519 enum => [qw(veth)],
520 },
521 name => {
522 type => 'string',
a069f163
DM
523 format_description => 'string',
524 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
525 pattern => '[-_.\w\d]+',
526 },
527 bridge => {
528 type => 'string',
a069f163 529 format_description => 'bridge',
1b4cf758
FG
530 description => 'Bridge to attach the network device to.',
531 pattern => '[-_.\w\d]+',
532 optional => 1,
533 },
534 hwaddr => {
535 type => 'string',
a069f163 536 format_description => "XX:XX:XX:XX:XX:XX",
e6f20294 537 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
538 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
539 optional => 1,
540 },
541 mtu => {
542 type => 'integer',
1b4cf758
FG
543 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
544 minimum => 64, # minimum ethernet frame is 64 bytes
545 optional => 1,
546 },
547 ip => {
548 type => 'string',
549 format => 'pve-ipv4-config',
718a67d6 550 format_description => '(IPv4/CIDR|dhcp|manual)',
1b4cf758
FG
551 description => 'IPv4 address in CIDR format.',
552 optional => 1,
553 },
554 gw => {
555 type => 'string',
556 format => 'ipv4',
557 format_description => 'GatewayIPv4',
558 description => 'Default gateway for IPv4 traffic.',
559 optional => 1,
560 },
561 ip6 => {
562 type => 'string',
563 format => 'pve-ipv6-config',
6ea7095c 564 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
1b4cf758
FG
565 description => 'IPv6 address in CIDR format.',
566 optional => 1,
567 },
568 gw6 => {
569 type => 'string',
570 format => 'ipv6',
571 format_description => 'GatewayIPv6',
572 description => 'Default gateway for IPv6 traffic.',
573 optional => 1,
574 },
575 firewall => {
576 type => 'boolean',
1b4cf758
FG
577 description => "Controls whether this interface's firewall rules should be used.",
578 optional => 1,
579 },
580 tag => {
581 type => 'integer',
6b202dd5
DM
582 minimum => 1,
583 maximum => 4094,
1b4cf758
FG
584 description => "VLAN tag for this interface.",
585 optional => 1,
586 },
587 trunks => {
588 type => 'string',
589 pattern => qr/\d+(?:;\d+)*/,
590 format_description => 'vlanid[;vlanid...]',
591 description => "VLAN ids to pass through the interface",
592 optional => 1,
593 },
380962c7
WB
594 rate => {
595 type => 'number',
596 format_description => 'mbps',
597 description => "Apply rate limiting to the interface",
598 optional => 1,
599 },
1b4cf758
FG
600};
601PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
602
603my $MAX_LXC_NETWORKS = 10;
604for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
605 $confdesc->{"net$i"} = {
606 optional => 1,
607 type => 'string', format => $netconf_desc,
608 description => "Specifies network interfaces for the container.",
609 };
610}
611
612PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
613sub verify_lxc_mp_string {
614 my ($mp, $noerr) = @_;
615
616 # do not allow:
617 # /./ or /../
618 # /. or /.. at the end
619 # ../ at the beginning
620
621 if($mp =~ m@/\.\.?/@ ||
622 $mp =~ m@/\.\.?$@ ||
623 $mp =~ m@^\.\./@) {
624 return undef if $noerr;
625 die "$mp contains illegal character sequences\n";
626 }
627 return $mp;
628}
629
630my $mp_desc = {
631 %$rootfs_desc,
84820d40
DM
632 backup => {
633 type => 'boolean',
235dbdf3
FG
634 description => 'Whether to include the mount point in backups.',
635 verbose_description => 'Whether to include the mount point in backups '.
636 '(only used for volume mount points).',
84820d40
DM
637 optional => 1,
638 },
1b4cf758
FG
639 mp => {
640 type => 'string',
641 format => 'pve-lxc-mp-string',
642 format_description => 'Path',
235dbdf3 643 description => 'Path to the mount point as seen from inside the container '.
52b6f941 644 '(must not contain symlinks).',
235dbdf3 645 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
52b6f941 646 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
647 },
648};
649PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
650
651my $unuseddesc = {
652 optional => 1,
653 type => 'string', format => 'pve-volume-id',
2928e616 654 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
1b4cf758
FG
655};
656
657for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
658 $confdesc->{"mp$i"} = {
659 optional => 1,
660 type => 'string', format => $mp_desc,
2928e616 661 description => "Use volume as container mount point.",
1b4cf758
FG
662 optional => 1,
663 };
664}
665
666for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
667 $confdesc->{"unused$i"} = $unuseddesc;
668}
669
670sub parse_pct_config {
671 my ($filename, $raw) = @_;
672
673 return undef if !defined($raw);
674
675 my $res = {
676 digest => Digest::SHA::sha1_hex($raw),
677 snapshots => {},
678 };
679
680 $filename =~ m|/lxc/(\d+).conf$|
681 || die "got strange filename '$filename'";
682
683 my $vmid = $1;
684
685 my $conf = $res;
686 my $descr = '';
687 my $section = '';
688
689 my @lines = split(/\n/, $raw);
690 foreach my $line (@lines) {
691 next if $line =~ m/^\s*$/;
692
693 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
694 $section = $1;
695 $conf->{description} = $descr if $descr;
696 $descr = '';
697 $conf = $res->{snapshots}->{$section} = {};
698 next;
699 }
700
701 if ($line =~ m/^\#(.*)\s*$/) {
702 $descr .= PVE::Tools::decode_text($1) . "\n";
703 next;
704 }
705
706 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
707 my $key = $1;
708 my $value = $3;
108c6cab
WB
709 my $validity = is_valid_lxc_conf_key($vmid, $key);
710 if ($validity eq 1) {
1b4cf758
FG
711 push @{$conf->{lxc}}, [$key, $value];
712 } elsif (my $errmsg = $validity) {
713 warn "vm $vmid - $key: $errmsg\n";
714 } else {
715 warn "vm $vmid - unable to parse config: $line\n";
716 }
717 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
718 $descr .= PVE::Tools::decode_text($2);
719 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
720 $conf->{snapstate} = $1;
721 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
722 my $key = $1;
723 my $value = $2;
724 eval { $value = PVE::LXC::Config->check_type($key, $value); };
725 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
726 $conf->{$key} = $value;
727 } else {
728 warn "vm $vmid - unable to parse config: $line\n";
729 }
730 }
731
732 $conf->{description} = $descr if $descr;
733
734 delete $res->{snapstate}; # just to be sure
735
736 return $res;
737}
738
739sub write_pct_config {
740 my ($filename, $conf) = @_;
741
742 delete $conf->{snapstate}; # just to be sure
743
744 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
745 my $used_volids = {};
746 foreach my $vid (@$volidlist) {
747 $used_volids->{$vid} = 1;
748 }
749
750 # remove 'unusedX' settings if the volume is still used
751 foreach my $key (keys %$conf) {
752 my $value = $conf->{$key};
753 if ($key =~ m/^unused/ && $used_volids->{$value}) {
754 delete $conf->{$key};
755 }
756 }
757
758 my $generate_raw_config = sub {
759 my ($conf) = @_;
760
761 my $raw = '';
762
763 # add description as comment to top of file
764 my $descr = $conf->{description} || '';
765 foreach my $cl (split(/\n/, $descr)) {
766 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
767 }
768
769 foreach my $key (sort keys %$conf) {
770 next if $key eq 'digest' || $key eq 'description' ||
771 $key eq 'pending' || $key eq 'snapshots' ||
772 $key eq 'snapname' || $key eq 'lxc';
773 my $value = $conf->{$key};
774 die "detected invalid newline inside property '$key'\n"
775 if $value =~ m/\n/;
776 $raw .= "$key: $value\n";
777 }
778
779 if (my $lxcconf = $conf->{lxc}) {
780 foreach my $entry (@$lxcconf) {
781 my ($k, $v) = @$entry;
782 $raw .= "$k: $v\n";
783 }
784 }
785
786 return $raw;
787 };
788
789 my $raw = &$generate_raw_config($conf);
790
791 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
792 $raw .= "\n[$snapname]\n";
793 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
794 }
795
796 return $raw;
797}
798
799sub update_pct_config {
800 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
801
802 my @nohotplug;
803
804 my $new_disks = 0;
805 my @deleted_volumes;
806
807 my $rootdir;
808 if ($running) {
809 my $pid = PVE::LXC::find_lxc_pid($vmid);
810 $rootdir = "/proc/$pid/root";
811 }
812
813 my $hotplug_error = sub {
814 if ($running) {
815 push @nohotplug, @_;
816 return 1;
817 } else {
818 return 0;
819 }
820 };
821
822 if (defined($delete)) {
823 foreach my $opt (@$delete) {
824 if (!exists($conf->{$opt})) {
a4a75420 825 # silently ignore
1b4cf758
FG
826 next;
827 }
828
e96a0ceb 829 if ($opt eq 'memory' || $opt eq 'rootfs') {
1b4cf758 830 die "unable to delete required option '$opt'\n";
e96a0ceb
DC
831 } elsif ($opt eq 'hostname') {
832 delete $conf->{$opt};
1b4cf758
FG
833 } elsif ($opt eq 'swap') {
834 delete $conf->{$opt};
835 PVE::LXC::write_cgroup_value("memory", $vmid,
836 "memory.memsw.limit_in_bytes", -1);
837 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
838 delete $conf->{$opt};
839 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
840 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
841 next if $hotplug_error->($opt);
842 delete $conf->{$opt};
f2357408
DM
843 } elsif ($opt eq 'cores') {
844 delete $conf->{$opt}; # rest is handled by pvestatd
d13b770f 845 } elsif ($opt eq 'cpulimit') {
213d70e6 846 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
d13b770f
WB
847 delete $conf->{$opt};
848 } elsif ($opt eq 'cpuunits') {
849 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
850 delete $conf->{$opt};
1b4cf758
FG
851 } elsif ($opt =~ m/^net(\d)$/) {
852 delete $conf->{$opt};
853 next if !$running;
854 my $netid = $1;
855 PVE::Network::veth_delete("veth${vmid}i$netid");
856 } elsif ($opt eq 'protection') {
857 delete $conf->{$opt};
858 } elsif ($opt =~ m/^unused(\d+)$/) {
859 next if $hotplug_error->($opt);
860 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
861 push @deleted_volumes, $conf->{$opt};
862 delete $conf->{$opt};
863 } elsif ($opt =~ m/^mp(\d+)$/) {
864 next if $hotplug_error->($opt);
865 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
866 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
867 delete $conf->{$opt};
868 if ($mp->{type} eq 'volume') {
869 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
870 }
871 } elsif ($opt eq 'unprivileged') {
872 die "unable to delete read-only option: '$opt'\n";
873 } else {
874 die "implement me (delete: $opt)"
875 }
876 PVE::LXC::Config->write_config($vmid, $conf) if $running;
877 }
878 }
879
880 # There's no separate swap size to configure, there's memory and "total"
881 # memory (iow. memory+swap). This means we have to change them together.
882 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
883 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
884 if (defined($wanted_memory) || defined($wanted_swap)) {
885
886 my $old_memory = ($conf->{memory} || 512);
887 my $old_swap = ($conf->{swap} || 0);
888
889 $wanted_memory //= $old_memory;
890 $wanted_swap //= $old_swap;
891
892 my $total = $wanted_memory + $wanted_swap;
893 if ($running) {
894 my $old_total = $old_memory + $old_swap;
895 if ($total > $old_total) {
896 PVE::LXC::write_cgroup_value("memory", $vmid,
897 "memory.memsw.limit_in_bytes",
898 int($total*1024*1024));
899 PVE::LXC::write_cgroup_value("memory", $vmid,
900 "memory.limit_in_bytes",
901 int($wanted_memory*1024*1024));
902 } else {
903 PVE::LXC::write_cgroup_value("memory", $vmid,
904 "memory.limit_in_bytes",
905 int($wanted_memory*1024*1024));
906 PVE::LXC::write_cgroup_value("memory", $vmid,
907 "memory.memsw.limit_in_bytes",
908 int($total*1024*1024));
909 }
910 }
911 $conf->{memory} = $wanted_memory;
912 $conf->{swap} = $wanted_swap;
913
914 PVE::LXC::Config->write_config($vmid, $conf) if $running;
915 }
916
f78c87a8
DM
917 my $storecfg = PVE::Storage::config();
918
1b4cf758 919 my $used_volids = {};
1b3213ae
FG
920 my $check_content_type = sub {
921 my ($mp) = @_;
922 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
f78c87a8 923 my $storage_config = PVE::Storage::storage_config($storecfg, $sid);
1b3213ae
FG
924 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
925 if !$storage_config->{content}->{rootdir};
926 };
1b4cf758 927
2718eec6
FG
928 my $rescan_volume = sub {
929 my ($mp) = @_;
930 eval {
931 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
932 if !defined($mp->{size});
933 };
934 warn "Could not rescan volume size - $@\n" if $@;
935 };
936
1b4cf758
FG
937 foreach my $opt (keys %$param) {
938 my $value = $param->{$opt};
939 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
f31bd6ae 940 if ($opt eq 'hostname' || $opt eq 'arch') {
1b4cf758
FG
941 $conf->{$opt} = $value;
942 } elsif ($opt eq 'onboot') {
943 $conf->{$opt} = $value ? 1 : 0;
944 } elsif ($opt eq 'startup') {
945 $conf->{$opt} = $value;
946 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
947 next if $hotplug_error->($opt);
948 $conf->{$opt} = $value;
949 } elsif ($opt eq 'nameserver') {
950 next if $hotplug_error->($opt);
951 my $list = PVE::LXC::verify_nameserver_list($value);
952 $conf->{$opt} = $list;
953 } elsif ($opt eq 'searchdomain') {
954 next if $hotplug_error->($opt);
955 my $list = PVE::LXC::verify_searchdomain_list($value);
956 $conf->{$opt} = $list;
f2357408
DM
957 } elsif ($opt eq 'cores') {
958 $conf->{$opt} = $value;# rest is handled by pvestatd
1b4cf758 959 } elsif ($opt eq 'cpulimit') {
115e5862
FG
960 if ($value == 0) {
961 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
962 } else {
963 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
964 }
1b4cf758
FG
965 $conf->{$opt} = $value;
966 } elsif ($opt eq 'cpuunits') {
967 $conf->{$opt} = $value;
968 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
969 } elsif ($opt eq 'description') {
d9c44e56 970 $conf->{$opt} = $value;
1b4cf758
FG
971 } elsif ($opt =~ m/^net(\d+)$/) {
972 my $netid = $1;
973 my $net = PVE::LXC::Config->parse_lxc_network($value);
974 if (!$running) {
975 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
976 } else {
977 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
978 }
979 } elsif ($opt eq 'protection') {
980 $conf->{$opt} = $value ? 1 : 0;
981 } elsif ($opt =~ m/^mp(\d+)$/) {
982 next if $hotplug_error->($opt);
983 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
984 my $old = $conf->{$opt};
56d7fc1b
FG
985 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
986 if ($mp->{type} eq 'volume') {
1b3213ae 987 &$check_content_type($mp);
56d7fc1b 988 $used_volids->{$mp->{volume}} = 1;
2718eec6
FG
989 &$rescan_volume($mp);
990 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
991 } else {
992 $conf->{$opt} = $value;
56d7fc1b 993 }
1b4cf758
FG
994 if (defined($old)) {
995 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
996 if ($mp->{type} eq 'volume') {
997 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
998 }
999 }
1000 $new_disks = 1;
1b4cf758
FG
1001 } elsif ($opt eq 'rootfs') {
1002 next if $hotplug_error->($opt);
1003 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1004 my $old = $conf->{$opt};
56d7fc1b
FG
1005 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
1006 if ($mp->{type} eq 'volume') {
1b3213ae 1007 &$check_content_type($mp);
56d7fc1b 1008 $used_volids->{$mp->{volume}} = 1;
2718eec6
FG
1009 &$rescan_volume($mp);
1010 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
1011 } else {
1012 $conf->{$opt} = $value;
56d7fc1b 1013 }
1b4cf758
FG
1014 if (defined($old)) {
1015 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
1016 if ($mp->{type} eq 'volume') {
1017 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1018 }
1019 }
1d9369fd 1020 $new_disks = 1;
1b4cf758
FG
1021 } elsif ($opt eq 'unprivileged') {
1022 die "unable to modify read-only option: '$opt'\n";
1023 } elsif ($opt eq 'ostype') {
1024 next if $hotplug_error->($opt);
1025 $conf->{$opt} = $value;
1026 } else {
1027 die "implement me: $opt";
1028 }
dc692997 1029
76ec0820 1030 PVE::LXC::Config->write_config($vmid, $conf) if $running;
f8aa3d35
WL
1031 }
1032
1b4cf758
FG
1033 # Apply deletions and creations of new volumes
1034 if (@deleted_volumes) {
1035 my $storage_cfg = PVE::Storage::config();
1036 foreach my $volume (@deleted_volumes) {
1037 next if $used_volids->{$volume}; # could have been re-added, too
1038 # also check for references in snapshots
1039 next if $class->is_volume_in_use($conf, $volume, 1);
1040 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1041 }
1042 }
1043
1044 if ($new_disks) {
1045 my $storage_cfg = PVE::Storage::config();
1046 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
1047 }
1048
1049 # This should be the last thing we do here
1050 if ($running && scalar(@nohotplug)) {
1051 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1052 }
1053}
1054
1055sub check_type {
1056 my ($class, $key, $value) = @_;
1057
1058 die "unknown setting '$key'\n" if !$confdesc->{$key};
1059
1060 my $type = $confdesc->{$key}->{type};
1061
1062 if (!defined($value)) {
1063 die "got undefined value\n";
1064 }
1065
1066 if ($value =~ m/[\n\r]/) {
1067 die "property contains a line feed\n";
1068 }
1069
1070 if ($type eq 'boolean') {
1071 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
1072 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
1073 die "type check ('boolean') failed - got '$value'\n";
1074 } elsif ($type eq 'integer') {
1075 return int($1) if $value =~ m/^(\d+)$/;
1076 die "type check ('integer') failed - got '$value'\n";
1077 } elsif ($type eq 'number') {
1078 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
1079 die "type check ('number') failed - got '$value'\n";
1080 } elsif ($type eq 'string') {
1081 if (my $fmt = $confdesc->{$key}->{format}) {
1082 PVE::JSONSchema::check_format($fmt, $value);
1083 return $value;
1084 }
1085 return $value;
1086 } else {
1087 die "internal error"
1088 }
1089}
1090
1091
1092# add JSON properties for create and set function
1093sub json_config_properties {
1094 my ($class, $prop) = @_;
1095
1096 foreach my $opt (keys %$confdesc) {
1097 next if $opt eq 'parent' || $opt eq 'snaptime';
1098 next if $prop->{$opt};
1099 $prop->{$opt} = $confdesc->{$opt};
1100 }
1101
1102 return $prop;
1103}
1104
1105sub __parse_ct_mountpoint_full {
1106 my ($class, $desc, $data, $noerr) = @_;
1107
1108 $data //= '';
1109
1110 my $res;
1111 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1112 if ($@) {
1113 return undef if $noerr;
1114 die $@;
1115 }
1116
1117 if (defined(my $size = $res->{size})) {
1118 $size = PVE::JSONSchema::parse_size($size);
1119 if (!defined($size)) {
1120 return undef if $noerr;
1121 die "invalid size: $size\n";
1122 }
1123 $res->{size} = $size;
1124 }
1125
1126 $res->{type} = $class->classify_mountpoint($res->{volume});
1127
1128 return $res;
1129};
1130
1131sub parse_ct_rootfs {
1132 my ($class, $data, $noerr) = @_;
1133
1134 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1135
1136 $res->{mp} = '/' if defined($res);
1137
1138 return $res;
1139}
1140
1141sub parse_ct_mountpoint {
1142 my ($class, $data, $noerr) = @_;
1143
1144 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1145}
1146
1147sub print_ct_mountpoint {
1148 my ($class, $info, $nomp) = @_;
1149 my $skip = [ 'type' ];
1150 push @$skip, 'mp' if $nomp;
1151 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1152}
1153
1154sub print_lxc_network {
1155 my ($class, $net) = @_;
1156 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1157}
1158
1159sub parse_lxc_network {
1160 my ($class, $data) = @_;
1161
1162 my $res = {};
1163
1164 return $res if !$data;
1165
1166 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1167
1168 $res->{type} = 'veth';
2f19133b
WB
1169 if (!$res->{hwaddr}) {
1170 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1171 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1172 }
1b4cf758
FG
1173
1174 return $res;
1175}
1176
1177sub option_exists {
1178 my ($class, $name) = @_;
1179
1180 return defined($confdesc->{$name});
1181}
1182# END JSON config code
1183
d250604f
FG
1184sub classify_mountpoint {
1185 my ($class, $vol) = @_;
1186 if ($vol =~ m!^/!) {
1187 return 'device' if $vol =~ m!^/dev/!;
1188 return 'bind';
1189 }
1190 return 'volume';
1191}
1192
72e6bc20
WB
1193my $is_volume_in_use = sub {
1194 my ($class, $config, $volid) = @_;
d250604f
FG
1195 my $used = 0;
1196
1197 $class->foreach_mountpoint($config, sub {
1198 my ($ms, $mountpoint) = @_;
1199 return if $used;
1200 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1201 });
1202
72e6bc20
WB
1203 return $used;
1204};
1205
1206sub is_volume_in_use_by_snapshots {
1207 my ($class, $config, $volid) = @_;
1208
1209 if (my $snapshots = $config->{snapshots}) {
d250604f 1210 foreach my $snap (keys %$snapshots) {
72e6bc20 1211 return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
d250604f
FG
1212 }
1213 }
1214
72e6bc20
WB
1215 return 0;
1216};
1217
1218sub is_volume_in_use {
1219 my ($class, $config, $volid, $include_snapshots) = @_;
1220 return 1 if $is_volume_in_use->($class, $config, $volid);
1221 return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
1222 return 0;
d250604f
FG
1223}
1224
1225sub has_dev_console {
1226 my ($class, $conf) = @_;
1227
1228 return !(defined($conf->{console}) && !$conf->{console});
1229}
1230
be7942f0
DM
1231sub has_lxc_entry {
1232 my ($class, $conf, $keyname) = @_;
1233
1234 if (my $lxcconf = $conf->{lxc}) {
1235 foreach my $entry (@$lxcconf) {
1236 my ($key, undef) = @$entry;
1237 return 1 if $key eq $keyname;
1238 }
1239 }
1240
1241 return 0;
1242}
1243
1b4cf758
FG
1244sub get_tty_count {
1245 my ($class, $conf) = @_;
1246
1247 return $conf->{tty} // $confdesc->{tty}->{default};
1248}
1249
1250sub get_cmode {
1251 my ($class, $conf) = @_;
1252
1253 return $conf->{cmode} // $confdesc->{cmode}->{default};
1254}
1255
d250604f
FG
1256sub mountpoint_names {
1257 my ($class, $reverse) = @_;
1258
1259 my @names = ('rootfs');
1260
1261 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1262 push @names, "mp$i";
1263 }
1264
1265 return $reverse ? reverse @names : @names;
1266}
1267
1268sub foreach_mountpoint_full {
8915b450 1269 my ($class, $conf, $reverse, $func, @param) = @_;
d250604f 1270
717f70b7
FG
1271 my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
1272 foreach my $key (@$mps) {
d250604f 1273 my $value = $conf->{$key};
1b4cf758 1274 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
d250604f
FG
1275 next if !defined($mountpoint);
1276
8915b450 1277 &$func($key, $mountpoint, @param);
d250604f
FG
1278 }
1279}
1280
1281sub foreach_mountpoint {
8915b450 1282 my ($class, $conf, $func, @param) = @_;
d250604f 1283
8915b450 1284 $class->foreach_mountpoint_full($conf, 0, $func, @param);
d250604f
FG
1285}
1286
1287sub foreach_mountpoint_reverse {
8915b450 1288 my ($class, $conf, $func, @param) = @_;
d250604f 1289
8915b450 1290 $class->foreach_mountpoint_full($conf, 1, $func, @param);
d250604f
FG
1291}
1292
1293sub get_vm_volumes {
1294 my ($class, $conf, $excludes) = @_;
1295
1296 my $vollist = [];
1297
1298 $class->foreach_mountpoint($conf, sub {
1299 my ($ms, $mountpoint) = @_;
1300
1301 return if $excludes && $ms eq $excludes;
1302
1303 my $volid = $mountpoint->{volume};
1304 return if !$volid || $mountpoint->{type} ne 'volume';
1305
1306 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1307 return if !$sid;
1308
1309 push @$vollist, $volid;
1310 });
1311
1312 return $vollist;
1313}
1314
f78c87a8 1315sub get_replicatable_volumes {
70996986 1316 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
f78c87a8
DM
1317
1318 my $volhash = {};
1319
1320 my $test_volid = sub {
1321 my ($volid, $mountpoint) = @_;
1322
1323 return if !$volid;
1324
5cf90b0c 1325 my $mptype = $mountpoint->{type};
896cd762 1326 my $replicate = $mountpoint->{replicate} // 1;
d2a046b7
DC
1327
1328 if ($mptype ne 'volume') {
1329 # skip bindmounts if replicate = 0 even for cleanup,
1330 # since bind mounts could not have been replicated ever
1331 return if !$replicate;
1332 die "unable to replicate mountpoint type '$mptype'\n";
1333 }
5cf90b0c
DM
1334
1335 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
1336 return if !$storeid;
1337
af21e699 1338 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
5cf90b0c
DM
1339 return if $scfg->{shared};
1340
1341 my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid);
1342 return if !$owner || ($owner != $vmid);
1343
1344 die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
1345
d2a046b7 1346 return if !$cleanup && !$replicate;
f78c87a8
DM
1347
1348 if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
e65dce6b 1349 return if $cleanup || $noerr;
f78c87a8
DM
1350 die "missing replicate feature on volume '$volid'\n";
1351 }
1352
1353 $volhash->{$volid} = 1;
1354 };
1355
1356 $class->foreach_mountpoint($conf, sub {
1357 my ($ms, $mountpoint) = @_;
1358 $test_volid->($mountpoint->{volume}, $mountpoint);
1359 });
1360
1361 foreach my $snapname (keys %{$conf->{snapshots}}) {
1362 my $snap = $conf->{snapshots}->{$snapname};
1363 $class->foreach_mountpoint($snap, sub {
1364 my ($ms, $mountpoint) = @_;
1365 $test_volid->($mountpoint->{volume}, $mountpoint);
1366 });
1367 }
1368
b03664ae
DM
1369 # add 'unusedX' volumes to volhash
1370 foreach my $key (keys %$conf) {
1371 if ($key =~ m/^unused/) {
1372 $test_volid->($conf->{$key}, { type => 'volume', replicate => 1 });
1373 }
1374 }
1375
f78c87a8
DM
1376 return $volhash;
1377}
1378
13791;