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