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