]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
depreacate pve-lxc-snapshot-name in favor of identical pve-snapshot-name
[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);
1a416433 8use PVE::GuestHelpers;
67afe46e
FG
9use PVE::INotify;
10use PVE::JSONSchema qw(get_standard_option);
11use PVE::Tools;
12
13use base qw(PVE::AbstractConfig);
14
15my $nodename = PVE::INotify::nodename();
16my $lock_handles = {};
17my $lockdir = "/run/lock/lxc";
18mkdir $lockdir;
19mkdir "/etc/pve/nodes/$nodename/lxc";
717f70b7 20my $MAX_MOUNT_POINTS = 256;
67afe46e
FG
21my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
22
23# BEGIN implemented abstract methods from PVE::AbstractConfig
24
25sub guest_type {
26 return "CT";
27}
28
4518000b
FG
29sub __config_max_unused_disks {
30 my ($class) = @_;
31
32 return $MAX_UNUSED_DISKS;
33}
34
67afe46e
FG
35sub config_file_lock {
36 my ($class, $vmid) = @_;
37
38 return "$lockdir/pve-config-${vmid}.lock";
39}
40
41sub cfs_config_path {
42 my ($class, $vmid, $node) = @_;
43
44 $node = $nodename if !$node;
45 return "nodes/$node/lxc/$vmid.conf";
46}
47
1a8269bc 48sub mountpoint_backup_enabled {
ec7f0f09 49 my ($class, $mp_key, $mountpoint) = @_;
1a8269bc
DM
50
51 return 1 if $mp_key eq 'rootfs';
52
f59c9670
FG
53 return 0 if $mountpoint->{type} ne 'volume';
54
1a8269bc
DM
55 return 1 if $mountpoint->{backup};
56
57 return 0;
58}
59
4518000b
FG
60sub has_feature {
61 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
62 my $err;
63
d250604f 64 $class->foreach_mountpoint($conf, sub {
4518000b
FG
65 my ($ms, $mountpoint) = @_;
66
67 return if $err; # skip further test
ec7f0f09 68 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
4518000b
FG
69
70 $err = 1
71 if !PVE::Storage::volume_has_feature($storecfg, $feature,
72 $mountpoint->{volume},
73 $snapname, $running);
74 });
75
76 return $err ? 0 : 1;
77}
78
79sub __snapshot_save_vmstate {
80 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
81 die "implement me - snapshot_save_vmstate\n";
82}
83
84sub __snapshot_check_running {
85 my ($class, $vmid) = @_;
86 return PVE::LXC::check_running($vmid);
87}
88
89sub __snapshot_check_freeze_needed {
90 my ($class, $vmid, $config, $save_vmstate) = @_;
91
92 my $ret = $class->__snapshot_check_running($vmid);
93 return ($ret, $ret);
94}
95
96sub __snapshot_freeze {
97 my ($class, $vmid, $unfreeze) = @_;
98
99 if ($unfreeze) {
100 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
101 warn $@ if $@;
102 } else {
103 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
104 PVE::LXC::sync_container_namespace($vmid);
105 }
106}
107
108sub __snapshot_create_vol_snapshot {
109 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
110
111 my $storecfg = PVE::Storage::config();
112
1a8269bc 113 return if $snapname eq 'vzdump' &&
ec7f0f09 114 !$class->mountpoint_backup_enabled($ms, $mountpoint);
1a8269bc 115
4518000b
FG
116 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
117}
118
119sub __snapshot_delete_remove_drive {
120 my ($class, $snap, $remove_drive) = @_;
121
122 if ($remove_drive eq 'vmstate') {
123 die "implement me - saving vmstate\n";
124 } else {
125 my $value = $snap->{$remove_drive};
1b4cf758 126 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
4518000b 127 delete $snap->{$remove_drive};
d103721f
FG
128
129 $class->add_unused_volume($snap, $mountpoint->{volume})
130 if ($mountpoint->{type} eq 'volume');
4518000b
FG
131 }
132}
133
134sub __snapshot_delete_vmstate_file {
135 my ($class, $snap, $force) = @_;
136
137 die "implement me - saving vmstate\n";
138}
139
140sub __snapshot_delete_vol_snapshot {
a8e9a4ea 141 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
4518000b 142
d103721f
FG
143 return if $snapname eq 'vzdump' &&
144 !$class->mountpoint_backup_enabled($ms, $mountpoint);
145
4518000b
FG
146 my $storecfg = PVE::Storage::config();
147 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
a8e9a4ea 148 push @$unused, $mountpoint->{volume};
4518000b
FG
149}
150
151sub __snapshot_rollback_vol_possible {
152 my ($class, $mountpoint, $snapname) = @_;
153
154 my $storecfg = PVE::Storage::config();
155 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
156}
157
158sub __snapshot_rollback_vol_rollback {
159 my ($class, $mountpoint, $snapname) = @_;
160
161 my $storecfg = PVE::Storage::config();
162 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
163}
164
165sub __snapshot_rollback_vm_stop {
166 my ($class, $vmid) = @_;
167
b1bad293 168 PVE::LXC::vm_stop($vmid, 1)
4518000b
FG
169 if $class->__snapshot_check_running($vmid);
170}
171
172sub __snapshot_rollback_vm_start {
67779c3c 173 my ($class, $vmid, $vmstate, $data);
4518000b
FG
174
175 die "implement me - save vmstate\n";
176}
177
a8656869
FG
178sub __snapshot_rollback_get_unused {
179 my ($class, $conf, $snap) = @_;
180
181 my $unused = [];
182
183 $class->__snapshot_foreach_volume($conf, sub {
184 my ($vs, $volume) = @_;
185
186 return if $volume->{type} ne 'volume';
187
188 my $found = 0;
189 my $volid = $volume->{volume};
190
191 $class->__snapshot_foreach_volume($snap, sub {
192 my ($ms, $mountpoint) = @_;
193
194 return if $found;
195 return if ($mountpoint->{type} ne 'volume');
196
197 $found = 1
198 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
199 });
200
201 push @$unused, $volid if !$found;
202 });
203
204 return $unused;
205}
206
4518000b
FG
207sub __snapshot_foreach_volume {
208 my ($class, $conf, $func) = @_;
209
d250604f 210 $class->foreach_mountpoint($conf, $func);
4518000b
FG
211}
212
67afe46e
FG
213# END implemented abstract methods from PVE::AbstractConfig
214
1b4cf758
FG
215# BEGIN JSON config code
216
217cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
218
219my $rootfs_desc = {
220 volume => {
221 type => 'string',
222 default_key => 1,
223 format => 'pve-lxc-mp-string',
224 format_description => 'volume',
225 description => 'Volume, device or directory to mount into the container.',
226 },
1b4cf758
FG
227 size => {
228 type => 'string',
229 format => 'disk-size',
230 format_description => 'DiskSize',
231 description => 'Volume size (read only value).',
232 optional => 1,
233 },
234 acl => {
235 type => 'boolean',
1b4cf758
FG
236 description => 'Explicitly enable or disable ACL support.',
237 optional => 1,
238 },
239 ro => {
240 type => 'boolean',
235dbdf3 241 description => 'Read-only mount point',
1b4cf758
FG
242 optional => 1,
243 },
244 quota => {
245 type => 'boolean',
1b4cf758
FG
246 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
247 optional => 1,
248 },
76ec0820 249 replicate => {
f8aa3d35
WL
250 type => 'boolean',
251 description => 'Will include this volume to a storage replica job.',
252 optional => 1,
253 default => 1,
254 },
552e168f
FG
255 shared => {
256 type => 'boolean',
257 description => 'Mark this non-volume mount point as available on multiple nodes (see \'nodes\')',
258 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!",
259 optional => 1,
260 default => 0,
261 },
1b4cf758
FG
262};
263
264PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
265 type => 'string', format => $rootfs_desc,
266 description => "Use volume as container root.",
267 optional => 1,
268});
269
5a63f1c5
WB
270my $features_desc = {
271 mount => {
272 optional => 1,
273 type => 'string',
274 description => "Allow mounting file systems of specific types."
275 ." This should be a list of file system types as used with the mount command."
276 ." Note that this can have negative effects on the container's security."
277 ." With access to a loop device, mounting a file can circumvent the mknod"
278 ." permission of the devices cgroup, mounting an NFS file system can"
279 ." block the host's I/O completely and prevent it from rebooting, etc.",
280 format_description => 'fstype;fstype;...',
e188f1bb 281 pattern => qr/[a-zA-Z0-9_; ]+/,
5a63f1c5
WB
282 },
283 nesting => {
284 optional => 1,
285 type => 'boolean',
286 default => 0,
287 description => "Allow nesting."
288 ." Best used with unprivileged containers with additional id mapping."
289 ." Note that this will expose procfs and sysfs contents of the host"
290 ." to the guest.",
291 },
292 keyctl => {
293 optional => 1,
294 type => 'boolean',
295 default => 0,
296 description => "For unprivileged containers only: Allow the use of the keyctl() system call."
297 ." This is required to use docker inside a container."
298 ." By default unprivileged containers will see this system call as non-existent."
299 ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
300 ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
301 ." Essentially, you can choose between running systemd-networkd or docker.",
302 },
96f8d2a2
WB
303 fuse => {
304 optional => 1,
305 type => 'boolean',
306 default => 0,
307 description => "Allow using 'fuse' file systems in a container."
308 ." Note that interactions between fuse and the freezer cgroup can potentially cause I/O deadlocks.",
309 },
5a63f1c5
WB
310};
311
1b4cf758
FG
312my $confdesc = {
313 lock => {
314 optional => 1,
315 type => 'string',
316 description => "Lock/unlock the VM.",
14c9b535 317 enum => [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
1b4cf758
FG
318 },
319 onboot => {
320 optional => 1,
321 type => 'boolean',
322 description => "Specifies whether a VM will be started during system bootup.",
323 default => 0,
324 },
325 startup => get_standard_option('pve-startup-order'),
326 template => {
327 optional => 1,
328 type => 'boolean',
329 description => "Enable/disable Template.",
330 default => 0,
331 },
332 arch => {
333 optional => 1,
334 type => 'string',
e1d54a38 335 enum => ['amd64', 'i386', 'arm64', 'armhf'],
1b4cf758
FG
336 description => "OS architecture type.",
337 default => 'amd64',
338 },
339 ostype => {
340 optional => 1,
341 type => 'string',
ed027b58 342 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
1b4cf758
FG
343 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.",
344 },
345 console => {
346 optional => 1,
347 type => 'boolean',
348 description => "Attach a console device (/dev/console) to the container.",
349 default => 1,
350 },
351 tty => {
352 optional => 1,
353 type => 'integer',
354 description => "Specify the number of tty available to the container",
355 minimum => 0,
356 maximum => 6,
357 default => 2,
358 },
f2357408
DM
359 cores => {
360 optional => 1,
361 type => 'integer',
362 description => "The number of cores assigned to the container. A container can use all available cores by default.",
363 minimum => 1,
364 maximum => 128,
365 },
1b4cf758
FG
366 cpulimit => {
367 optional => 1,
368 type => 'number',
064529c3 369 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
370 minimum => 0,
371 maximum => 128,
372 default => 0,
373 },
374 cpuunits => {
375 optional => 1,
376 type => 'integer',
377 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.",
378 minimum => 0,
379 maximum => 500000,
380 default => 1024,
381 },
382 memory => {
383 optional => 1,
384 type => 'integer',
385 description => "Amount of RAM for the VM in MB.",
386 minimum => 16,
387 default => 512,
388 },
389 swap => {
390 optional => 1,
391 type => 'integer',
392 description => "Amount of SWAP for the VM in MB.",
393 minimum => 0,
394 default => 512,
395 },
396 hostname => {
397 optional => 1,
398 description => "Set a host name for the container.",
399 type => 'string', format => 'dns-name',
400 maxLength => 255,
401 },
402 description => {
403 optional => 1,
404 type => 'string',
a069f163 405 description => "Container description. Only used on the configuration web interface.",
1b4cf758
FG
406 },
407 searchdomain => {
408 optional => 1,
409 type => 'string', format => 'dns-name-list',
410 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
411 },
412 nameserver => {
413 optional => 1,
414 type => 'string', format => 'address-list',
415 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.",
416 },
417 rootfs => get_standard_option('pve-ct-rootfs'),
418 parent => {
419 optional => 1,
420 type => 'string', format => 'pve-configid',
421 maxLength => 40,
422 description => "Parent snapshot name. This is used internally, and should not be modified.",
423 },
424 snaptime => {
425 optional => 1,
426 description => "Timestamp for snapshots.",
427 type => 'integer',
428 minimum => 0,
429 },
430 cmode => {
431 optional => 1,
432 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).",
433 type => 'string',
434 enum => ['shell', 'console', 'tty'],
435 default => 'tty',
436 },
437 protection => {
438 optional => 1,
439 type => 'boolean',
440 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
441 default => 0,
442 },
443 unprivileged => {
444 optional => 1,
445 type => 'boolean',
446 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
447 default => 0,
448 },
5a63f1c5
WB
449 features => {
450 optional => 1,
451 type => 'string',
452 format => $features_desc,
453 description => "Allow containers access to advanced features.",
454 },
1a416433
DC
455 hookscript => {
456 optional => 1,
457 type => 'string',
458 format => 'pve-volume-id',
459 description => 'Script that will be exectued during various steps in the containers lifetime.',
460 },
1b4cf758
FG
461};
462
463my $valid_lxc_conf_keys = {
108c6cab
WB
464 'lxc.apparmor.profile' => 1,
465 'lxc.apparmor.allow_incomplete' => 1,
d494e03c
WB
466 'lxc.apparmor.allow_nesting' => 1,
467 'lxc.apparmor.raw' => 1,
108c6cab 468 'lxc.selinux.context' => 1,
1b4cf758
FG
469 'lxc.include' => 1,
470 'lxc.arch' => 1,
108c6cab
WB
471 'lxc.uts.name' => 1,
472 'lxc.signal.halt' => 1,
473 'lxc.signal.reboot' => 1,
474 'lxc.signal.stop' => 1,
475 'lxc.init.cmd' => 1,
476 'lxc.pty.max' => 1,
1b4cf758 477 'lxc.console.logfile' => 1,
108c6cab
WB
478 'lxc.console.path' => 1,
479 'lxc.tty.max' => 1,
480 'lxc.devtty.dir' => 1,
1b4cf758
FG
481 'lxc.hook.autodev' => 1,
482 'lxc.autodev' => 1,
483 'lxc.kmsg' => 1,
108c6cab 484 'lxc.mount.fstab' => 1,
1b4cf758
FG
485 'lxc.mount.entry' => 1,
486 'lxc.mount.auto' => 1,
108c6cab 487 'lxc.rootfs.path' => 'lxc.rootfs.path is auto generated from rootfs',
1b4cf758
FG
488 'lxc.rootfs.mount' => 1,
489 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
235dbdf3 490 ', please use mount point options in the "rootfs" key',
1b4cf758 491 # lxc.cgroup.*
108c6cab 492 # lxc.prlimit.*
6eb23d1e 493 # lxc.net.*
1b4cf758
FG
494 'lxc.cap.drop' => 1,
495 'lxc.cap.keep' => 1,
108c6cab
WB
496 'lxc.seccomp.profile' => 1,
497 'lxc.idmap' => 1,
1b4cf758
FG
498 'lxc.hook.pre-start' => 1,
499 'lxc.hook.pre-mount' => 1,
500 'lxc.hook.mount' => 1,
501 'lxc.hook.start' => 1,
502 'lxc.hook.stop' => 1,
503 'lxc.hook.post-stop' => 1,
504 'lxc.hook.clone' => 1,
505 'lxc.hook.destroy' => 1,
108c6cab
WB
506 'lxc.log.level' => 1,
507 'lxc.log.file' => 1,
1b4cf758
FG
508 'lxc.start.auto' => 1,
509 'lxc.start.delay' => 1,
510 'lxc.start.order' => 1,
511 'lxc.group' => 1,
512 'lxc.environment' => 1,
d4a135f7
WB
513
514 # All these are namespaced via CLONE_NEWIPC (see namespaces(7)).
515 'lxc.sysctl.fs.mqueue' => 1,
516 'lxc.sysctl.kernel.msgmax' => 1,
517 'lxc.sysctl.kernel.msgmnb' => 1,
518 'lxc.sysctl.kernel.msgmni' => 1,
519 'lxc.sysctl.kernel.sem' => 1,
520 'lxc.sysctl.kernel.shmall' => 1,
521 'lxc.sysctl.kernel.shmmax' => 1,
522 'lxc.sysctl.kernel.shmmni' => 1,
523 'lxc.sysctl.kernel.shm_rmid_forced' => 1,
1b4cf758
FG
524};
525
108c6cab
WB
526my $deprecated_lxc_conf_keys = {
527 # Deprecated (removed with lxc 3.0):
528 'lxc.aa_profile' => 'lxc.apparmor.profile',
529 'lxc.aa_allow_incomplete' => 'lxc.apparmor.allow_incomplete',
530 'lxc.console' => 'lxc.console.path',
531 'lxc.devttydir' => 'lxc.tty.dir',
532 'lxc.haltsignal' => 'lxc.signal.halt',
533 'lxc.rebootsignal' => 'lxc.signal.reboot',
534 'lxc.stopsignal' => 'lxc.signal.stop',
535 'lxc.id_map' => 'lxc.idmap',
536 'lxc.init_cmd' => 'lxc.init.cmd',
537 'lxc.loglevel' => 'lxc.log.level',
538 'lxc.logfile' => 'lxc.log.file',
539 'lxc.mount' => 'lxc.mount.fstab',
540 'lxc.network.type' => 'lxc.net.INDEX.type',
541 'lxc.network.flags' => 'lxc.net.INDEX.flags',
542 'lxc.network.link' => 'lxc.net.INDEX.link',
543 'lxc.network.mtu' => 'lxc.net.INDEX.mtu',
544 'lxc.network.name' => 'lxc.net.INDEX.name',
545 'lxc.network.hwaddr' => 'lxc.net.INDEX.hwaddr',
546 'lxc.network.ipv4' => 'lxc.net.INDEX.ipv4.address',
547 'lxc.network.ipv4.gateway' => 'lxc.net.INDEX.ipv4.gateway',
548 'lxc.network.ipv6' => 'lxc.net.INDEX.ipv6.address',
549 'lxc.network.ipv6.gateway' => 'lxc.net.INDEX.ipv6.gateway',
550 'lxc.network.script.up' => 'lxc.net.INDEX.script.up',
551 'lxc.network.script.down' => 'lxc.net.INDEX.script.down',
552 'lxc.pts' => 'lxc.pty.max',
553 'lxc.se_context' => 'lxc.selinux.context',
554 'lxc.seccomp' => 'lxc.seccomp.profile',
555 'lxc.tty' => 'lxc.tty.max',
556 'lxc.utsname' => 'lxc.uts.name',
557};
558
559sub is_valid_lxc_conf_key {
560 my ($vmid, $key) = @_;
561 if ($key =~ /^lxc\.limit\./) {
562 warn "vm $vmid - $key: lxc.limit.* was renamed to lxc.prlimit.*\n";
563 return 1;
564 }
565 if (defined(my $new_name = $deprecated_lxc_conf_keys->{$key})) {
566 warn "vm $vmid - $key is deprecated and was renamed to $new_name\n";
567 return 1;
568 }
569 my $validity = $valid_lxc_conf_keys->{$key};
570 return $validity if defined($validity);
571 return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
572 || $key =~ /^lxc\.prlimit\./ # allow all prlimits
573 || $key =~ /^lxc\.net\./; # allow custom network definitions
574 return 0;
575}
576
5e5915c5 577our $netconf_desc = {
1b4cf758
FG
578 type => {
579 type => 'string',
580 optional => 1,
581 description => "Network interface type.",
582 enum => [qw(veth)],
583 },
584 name => {
585 type => 'string',
a069f163
DM
586 format_description => 'string',
587 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
588 pattern => '[-_.\w\d]+',
589 },
590 bridge => {
591 type => 'string',
a069f163 592 format_description => 'bridge',
1b4cf758
FG
593 description => 'Bridge to attach the network device to.',
594 pattern => '[-_.\w\d]+',
595 optional => 1,
596 },
603536e0 597 hwaddr => get_standard_option('mac-addr', {
e6f20294 598 description => 'The interface MAC address. This is dynamically allocated by default, but you can set that statically if needed, for example to always have the same link-local IPv6 address. (lxc.network.hwaddr)',
603536e0 599 }),
1b4cf758
FG
600 mtu => {
601 type => 'integer',
1b4cf758
FG
602 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
603 minimum => 64, # minimum ethernet frame is 64 bytes
604 optional => 1,
605 },
606 ip => {
607 type => 'string',
608 format => 'pve-ipv4-config',
718a67d6 609 format_description => '(IPv4/CIDR|dhcp|manual)',
1b4cf758
FG
610 description => 'IPv4 address in CIDR format.',
611 optional => 1,
612 },
613 gw => {
614 type => 'string',
615 format => 'ipv4',
616 format_description => 'GatewayIPv4',
617 description => 'Default gateway for IPv4 traffic.',
618 optional => 1,
619 },
620 ip6 => {
621 type => 'string',
622 format => 'pve-ipv6-config',
6ea7095c 623 format_description => '(IPv6/CIDR|auto|dhcp|manual)',
1b4cf758
FG
624 description => 'IPv6 address in CIDR format.',
625 optional => 1,
626 },
627 gw6 => {
628 type => 'string',
629 format => 'ipv6',
630 format_description => 'GatewayIPv6',
631 description => 'Default gateway for IPv6 traffic.',
632 optional => 1,
633 },
634 firewall => {
635 type => 'boolean',
1b4cf758
FG
636 description => "Controls whether this interface's firewall rules should be used.",
637 optional => 1,
638 },
639 tag => {
640 type => 'integer',
6b202dd5
DM
641 minimum => 1,
642 maximum => 4094,
1b4cf758
FG
643 description => "VLAN tag for this interface.",
644 optional => 1,
645 },
646 trunks => {
647 type => 'string',
648 pattern => qr/\d+(?:;\d+)*/,
649 format_description => 'vlanid[;vlanid...]',
650 description => "VLAN ids to pass through the interface",
651 optional => 1,
652 },
380962c7
WB
653 rate => {
654 type => 'number',
655 format_description => 'mbps',
656 description => "Apply rate limiting to the interface",
657 optional => 1,
658 },
1b4cf758
FG
659};
660PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
661
662my $MAX_LXC_NETWORKS = 10;
663for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
664 $confdesc->{"net$i"} = {
665 optional => 1,
666 type => 'string', format => $netconf_desc,
667 description => "Specifies network interfaces for the container.",
668 };
669}
670
671PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
672sub verify_lxc_mp_string {
673 my ($mp, $noerr) = @_;
674
675 # do not allow:
676 # /./ or /../
677 # /. or /.. at the end
678 # ../ at the beginning
679
680 if($mp =~ m@/\.\.?/@ ||
681 $mp =~ m@/\.\.?$@ ||
682 $mp =~ m@^\.\./@) {
683 return undef if $noerr;
684 die "$mp contains illegal character sequences\n";
685 }
686 return $mp;
687}
688
689my $mp_desc = {
690 %$rootfs_desc,
84820d40
DM
691 backup => {
692 type => 'boolean',
235dbdf3
FG
693 description => 'Whether to include the mount point in backups.',
694 verbose_description => 'Whether to include the mount point in backups '.
695 '(only used for volume mount points).',
84820d40
DM
696 optional => 1,
697 },
1b4cf758
FG
698 mp => {
699 type => 'string',
700 format => 'pve-lxc-mp-string',
701 format_description => 'Path',
235dbdf3 702 description => 'Path to the mount point as seen from inside the container '.
52b6f941 703 '(must not contain symlinks).',
235dbdf3 704 verbose_description => "Path to the mount point as seen from inside the container.\n\n".
52b6f941 705 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
706 },
707};
708PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
709
710my $unuseddesc = {
711 optional => 1,
712 type => 'string', format => 'pve-volume-id',
2928e616 713 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
1b4cf758
FG
714};
715
716for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
717 $confdesc->{"mp$i"} = {
718 optional => 1,
719 type => 'string', format => $mp_desc,
2928e616 720 description => "Use volume as container mount point.",
1b4cf758
FG
721 optional => 1,
722 };
723}
724
725for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
726 $confdesc->{"unused$i"} = $unuseddesc;
727}
728
729sub parse_pct_config {
730 my ($filename, $raw) = @_;
731
732 return undef if !defined($raw);
733
734 my $res = {
735 digest => Digest::SHA::sha1_hex($raw),
736 snapshots => {},
737 };
738
739 $filename =~ m|/lxc/(\d+).conf$|
740 || die "got strange filename '$filename'";
741
742 my $vmid = $1;
743
744 my $conf = $res;
745 my $descr = '';
746 my $section = '';
747
748 my @lines = split(/\n/, $raw);
749 foreach my $line (@lines) {
750 next if $line =~ m/^\s*$/;
751
752 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
753 $section = $1;
754 $conf->{description} = $descr if $descr;
755 $descr = '';
756 $conf = $res->{snapshots}->{$section} = {};
757 next;
758 }
759
760 if ($line =~ m/^\#(.*)\s*$/) {
761 $descr .= PVE::Tools::decode_text($1) . "\n";
762 next;
763 }
764
765 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
766 my $key = $1;
767 my $value = $3;
108c6cab
WB
768 my $validity = is_valid_lxc_conf_key($vmid, $key);
769 if ($validity eq 1) {
1b4cf758
FG
770 push @{$conf->{lxc}}, [$key, $value];
771 } elsif (my $errmsg = $validity) {
772 warn "vm $vmid - $key: $errmsg\n";
773 } else {
774 warn "vm $vmid - unable to parse config: $line\n";
775 }
776 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
777 $descr .= PVE::Tools::decode_text($2);
778 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
779 $conf->{snapstate} = $1;
780 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
781 my $key = $1;
782 my $value = $2;
783 eval { $value = PVE::LXC::Config->check_type($key, $value); };
784 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
785 $conf->{$key} = $value;
786 } else {
787 warn "vm $vmid - unable to parse config: $line\n";
788 }
789 }
790
791 $conf->{description} = $descr if $descr;
792
793 delete $res->{snapstate}; # just to be sure
794
795 return $res;
796}
797
798sub write_pct_config {
799 my ($filename, $conf) = @_;
800
801 delete $conf->{snapstate}; # just to be sure
802
803 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
804 my $used_volids = {};
805 foreach my $vid (@$volidlist) {
806 $used_volids->{$vid} = 1;
807 }
808
809 # remove 'unusedX' settings if the volume is still used
810 foreach my $key (keys %$conf) {
811 my $value = $conf->{$key};
812 if ($key =~ m/^unused/ && $used_volids->{$value}) {
813 delete $conf->{$key};
814 }
815 }
816
817 my $generate_raw_config = sub {
818 my ($conf) = @_;
819
820 my $raw = '';
821
822 # add description as comment to top of file
823 my $descr = $conf->{description} || '';
824 foreach my $cl (split(/\n/, $descr)) {
825 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
826 }
827
828 foreach my $key (sort keys %$conf) {
829 next if $key eq 'digest' || $key eq 'description' ||
830 $key eq 'pending' || $key eq 'snapshots' ||
831 $key eq 'snapname' || $key eq 'lxc';
832 my $value = $conf->{$key};
833 die "detected invalid newline inside property '$key'\n"
834 if $value =~ m/\n/;
835 $raw .= "$key: $value\n";
836 }
837
838 if (my $lxcconf = $conf->{lxc}) {
839 foreach my $entry (@$lxcconf) {
840 my ($k, $v) = @$entry;
841 $raw .= "$k: $v\n";
842 }
843 }
844
845 return $raw;
846 };
847
848 my $raw = &$generate_raw_config($conf);
849
850 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
851 $raw .= "\n[$snapname]\n";
852 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
853 }
854
855 return $raw;
856}
857
858sub update_pct_config {
859 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
860
861 my @nohotplug;
862
863 my $new_disks = 0;
864 my @deleted_volumes;
865
866 my $rootdir;
867 if ($running) {
868 my $pid = PVE::LXC::find_lxc_pid($vmid);
869 $rootdir = "/proc/$pid/root";
870 }
871
872 my $hotplug_error = sub {
873 if ($running) {
874 push @nohotplug, @_;
875 return 1;
876 } else {
877 return 0;
878 }
879 };
880
881 if (defined($delete)) {
882 foreach my $opt (@$delete) {
883 if (!exists($conf->{$opt})) {
a4a75420 884 # silently ignore
1b4cf758
FG
885 next;
886 }
887
e96a0ceb 888 if ($opt eq 'memory' || $opt eq 'rootfs') {
1b4cf758 889 die "unable to delete required option '$opt'\n";
e96a0ceb
DC
890 } elsif ($opt eq 'hostname') {
891 delete $conf->{$opt};
1b4cf758
FG
892 } elsif ($opt eq 'swap') {
893 delete $conf->{$opt};
894 PVE::LXC::write_cgroup_value("memory", $vmid,
895 "memory.memsw.limit_in_bytes", -1);
1a416433 896 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup' || $opt eq 'hookscript') {
1b4cf758
FG
897 delete $conf->{$opt};
898 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
899 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
900 next if $hotplug_error->($opt);
901 delete $conf->{$opt};
f2357408
DM
902 } elsif ($opt eq 'cores') {
903 delete $conf->{$opt}; # rest is handled by pvestatd
d13b770f 904 } elsif ($opt eq 'cpulimit') {
213d70e6 905 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
d13b770f
WB
906 delete $conf->{$opt};
907 } elsif ($opt eq 'cpuunits') {
908 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
909 delete $conf->{$opt};
1b4cf758
FG
910 } elsif ($opt =~ m/^net(\d)$/) {
911 delete $conf->{$opt};
912 next if !$running;
913 my $netid = $1;
914 PVE::Network::veth_delete("veth${vmid}i$netid");
915 } elsif ($opt eq 'protection') {
916 delete $conf->{$opt};
917 } elsif ($opt =~ m/^unused(\d+)$/) {
918 next if $hotplug_error->($opt);
919 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
920 push @deleted_volumes, $conf->{$opt};
921 delete $conf->{$opt};
922 } elsif ($opt =~ m/^mp(\d+)$/) {
923 next if $hotplug_error->($opt);
924 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
925 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
926 delete $conf->{$opt};
927 if ($mp->{type} eq 'volume') {
928 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
929 }
930 } elsif ($opt eq 'unprivileged') {
931 die "unable to delete read-only option: '$opt'\n";
5a63f1c5
WB
932 } elsif ($opt eq 'features') {
933 next if $hotplug_error->($opt);
934 delete $conf->{$opt};
1b4cf758
FG
935 } else {
936 die "implement me (delete: $opt)"
937 }
938 PVE::LXC::Config->write_config($vmid, $conf) if $running;
939 }
940 }
941
942 # There's no separate swap size to configure, there's memory and "total"
943 # memory (iow. memory+swap). This means we have to change them together.
944 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
945 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
946 if (defined($wanted_memory) || defined($wanted_swap)) {
947
948 my $old_memory = ($conf->{memory} || 512);
949 my $old_swap = ($conf->{swap} || 0);
950
951 $wanted_memory //= $old_memory;
952 $wanted_swap //= $old_swap;
953
954 my $total = $wanted_memory + $wanted_swap;
955 if ($running) {
956 my $old_total = $old_memory + $old_swap;
957 if ($total > $old_total) {
958 PVE::LXC::write_cgroup_value("memory", $vmid,
959 "memory.memsw.limit_in_bytes",
960 int($total*1024*1024));
961 PVE::LXC::write_cgroup_value("memory", $vmid,
962 "memory.limit_in_bytes",
963 int($wanted_memory*1024*1024));
964 } else {
965 PVE::LXC::write_cgroup_value("memory", $vmid,
966 "memory.limit_in_bytes",
967 int($wanted_memory*1024*1024));
968 PVE::LXC::write_cgroup_value("memory", $vmid,
969 "memory.memsw.limit_in_bytes",
970 int($total*1024*1024));
971 }
972 }
973 $conf->{memory} = $wanted_memory;
974 $conf->{swap} = $wanted_swap;
975
976 PVE::LXC::Config->write_config($vmid, $conf) if $running;
977 }
978
f78c87a8
DM
979 my $storecfg = PVE::Storage::config();
980
1b4cf758 981 my $used_volids = {};
1b3213ae
FG
982 my $check_content_type = sub {
983 my ($mp) = @_;
984 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
f78c87a8 985 my $storage_config = PVE::Storage::storage_config($storecfg, $sid);
1b3213ae
FG
986 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
987 if !$storage_config->{content}->{rootdir};
988 };
1b4cf758 989
2718eec6
FG
990 my $rescan_volume = sub {
991 my ($mp) = @_;
992 eval {
993 $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
994 if !defined($mp->{size});
995 };
996 warn "Could not rescan volume size - $@\n" if $@;
997 };
998
1b4cf758
FG
999 foreach my $opt (keys %$param) {
1000 my $value = $param->{$opt};
1001 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
f31bd6ae 1002 if ($opt eq 'hostname' || $opt eq 'arch') {
1b4cf758
FG
1003 $conf->{$opt} = $value;
1004 } elsif ($opt eq 'onboot') {
1005 $conf->{$opt} = $value ? 1 : 0;
1006 } elsif ($opt eq 'startup') {
1007 $conf->{$opt} = $value;
1008 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1009 next if $hotplug_error->($opt);
1010 $conf->{$opt} = $value;
1011 } elsif ($opt eq 'nameserver') {
1012 next if $hotplug_error->($opt);
1013 my $list = PVE::LXC::verify_nameserver_list($value);
1014 $conf->{$opt} = $list;
1015 } elsif ($opt eq 'searchdomain') {
1016 next if $hotplug_error->($opt);
1017 my $list = PVE::LXC::verify_searchdomain_list($value);
1018 $conf->{$opt} = $list;
f2357408
DM
1019 } elsif ($opt eq 'cores') {
1020 $conf->{$opt} = $value;# rest is handled by pvestatd
1b4cf758 1021 } elsif ($opt eq 'cpulimit') {
115e5862
FG
1022 if ($value == 0) {
1023 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1024 } else {
1025 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
1026 }
1b4cf758
FG
1027 $conf->{$opt} = $value;
1028 } elsif ($opt eq 'cpuunits') {
1029 $conf->{$opt} = $value;
1030 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1031 } elsif ($opt eq 'description') {
d9c44e56 1032 $conf->{$opt} = $value;
1b4cf758
FG
1033 } elsif ($opt =~ m/^net(\d+)$/) {
1034 my $netid = $1;
1035 my $net = PVE::LXC::Config->parse_lxc_network($value);
1036 if (!$running) {
1037 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
1038 } else {
1039 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1040 }
1041 } elsif ($opt eq 'protection') {
1042 $conf->{$opt} = $value ? 1 : 0;
1043 } elsif ($opt =~ m/^mp(\d+)$/) {
1044 next if $hotplug_error->($opt);
1045 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1046 my $old = $conf->{$opt};
56d7fc1b
FG
1047 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
1048 if ($mp->{type} eq 'volume') {
1b3213ae 1049 &$check_content_type($mp);
56d7fc1b 1050 $used_volids->{$mp->{volume}} = 1;
2718eec6
FG
1051 &$rescan_volume($mp);
1052 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
1053 } else {
1054 $conf->{$opt} = $value;
56d7fc1b 1055 }
1b4cf758
FG
1056 if (defined($old)) {
1057 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
1058 if ($mp->{type} eq 'volume') {
1059 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1060 }
1061 }
1062 $new_disks = 1;
1b4cf758
FG
1063 } elsif ($opt eq 'rootfs') {
1064 next if $hotplug_error->($opt);
1065 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
1066 my $old = $conf->{$opt};
56d7fc1b
FG
1067 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
1068 if ($mp->{type} eq 'volume') {
1b3213ae 1069 &$check_content_type($mp);
56d7fc1b 1070 $used_volids->{$mp->{volume}} = 1;
2718eec6
FG
1071 &$rescan_volume($mp);
1072 $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
1073 } else {
1074 $conf->{$opt} = $value;
56d7fc1b 1075 }
1b4cf758
FG
1076 if (defined($old)) {
1077 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
1078 if ($mp->{type} eq 'volume') {
1079 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
1080 }
1081 }
1d9369fd 1082 $new_disks = 1;
1b4cf758
FG
1083 } elsif ($opt eq 'unprivileged') {
1084 die "unable to modify read-only option: '$opt'\n";
1085 } elsif ($opt eq 'ostype') {
1086 next if $hotplug_error->($opt);
1087 $conf->{$opt} = $value;
5a63f1c5
WB
1088 } elsif ($opt eq 'features') {
1089 next if $hotplug_error->($opt);
1090 $conf->{$opt} = $value;
1a416433
DC
1091 } elsif ($opt eq 'hookscript') {
1092 PVE::GuestHelpers::check_hookscript($value);
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;